Po pierwsze przepraszam za długość tego pytania.
Jestem autorem IronScheme . Ostatnio ciężko pracowałem nad emitowaniem przyzwoitych informacji debugowania, aby móc używać „natywnego” debuggera .NET.
Chociaż częściowo się to udało, napotykam na początkowe problemy.
Pierwszy problem dotyczy steppingu.
Ponieważ Scheme jest językiem wyrażeń, wszystko jest zwykle zawijane w nawiasy, w przeciwieństwie do głównych języków .NET, które wydają się być oparte na instrukcjach (lub wierszach).
Oryginalny kod (Schemat) wygląda następująco:
(define (baz x)
(cond
[(null? x)
x]
[(pair? x)
(car x)]
[else
(assertion-violation #f "nooo" x)]))
Celowo umieściłem każde wyrażenie w nowej linii.
Emitowany kod przekształca się w C # (przez ILSpy) wygląda następująco:
public static object ::baz(object x)
{
if (x == null)
{
return x;
}
if (x is Cons)
{
return Builtins.Car(x);
}
return #.ironscheme.exceptions::assertion-violation+(
RuntimeHelpers.False, "nooo", Builtins.List(x));
}
Jak widać, całkiem proste.
Uwaga: Jeśli kod zostałby przekształcony w wyrażenie warunkowe (? :) w C #, całość byłaby tylko jednym krokiem debugowania, miej to na uwadze.
Oto wyjście IL z numerami źródła i linii:
.method public static object '::baz'(object x) cil managed
{
// Code size 56 (0x38)
.maxstack 6
.line 15,15 : 1,2 ''
//000014:
//000015: (define (baz x)
IL_0000: nop
.line 17,17 : 6,15 ''
//000016: (cond
//000017: [(null? x)
IL_0001: ldarg.0
IL_0002: brtrue IL_0009
.line 18,18 : 7,8 ''
//000018: x]
IL_0007: ldarg.0
IL_0008: ret
.line 19,19 : 6,15 ''
//000019: [(pair? x)
.line 19,19 : 6,15 ''
IL_0009: ldarg.0
IL_000a: isinst [IronScheme]IronScheme.Runtime.Cons
IL_000f: ldnull
IL_0010: cgt.un
IL_0012: brfalse IL_0020
IL_0017: ldarg.0
.line 20,20 : 7,14 ''
//000020: (car x)]
IL_0018: tail.
IL_001a: call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
IL_001f: ret
IL_0020: ldsfld object
[Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
IL_0025: ldstr "nooo"
IL_002a: ldarg.0
IL_002b: call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
.line 22,22 : 7,40 ''
//000021: [else
//000022: (assertion-violation #f "nooo" x)]))
IL_0030: tail.
IL_0032: call object [ironscheme.boot]#::
'ironscheme.exceptions::assertion-violation+'(object,object,object)
IL_0037: ret
} // end of method 'eval-core(033)'::'::baz'
Uwaga: Aby uniemożliwić debugerowi po prostu podświetlenie całej metody, ustawiam punkt wejścia metody na szerokość tylko 1 kolumny.
Jak widać, każde wyrażenie jest poprawnie odwzorowywane na linię.
Teraz problem z steppingiem (testowany na VS2010, ale ten sam / podobny problem na VS2008):
Nie są IgnoreSymbolStoreSequencePoints
one stosowane.
- Wywołaj baz z null arg, działa poprawnie. (null? x), po którym następuje x.
- Wywołaj baz z argumentem Cons, działa poprawnie. (null? x), a następnie (para? x), a następnie (samochód x).
- Zadzwoń do baz z innym argumentem, nie udaje się. (null? x), a następnie (para? x), a następnie (samochód x), a następnie (asercja-naruszenie ...).
Podczas aplikacji IgnoreSymbolStoreSequencePoints
(zgodnie z zaleceniami):
- Wywołaj baz z null arg, działa poprawnie. (null? x), po którym następuje x.
- Zadzwoń do baz z argumentem Cons, nie udaje się. (null? x) to (para? x).
- Zadzwoń do baz z innym argumentem, nie udaje się. (null? x), a następnie (para? x), a następnie (samochód x), a następnie (asercja-naruszenie ...).
W tym trybie stwierdzam również, że niektóre linie (nie pokazane tutaj) są nieprawidłowo podświetlone, są wyłączone o 1.
Oto kilka pomysłów, jakie mogą być przyczyny:
- Połączenia ogonowe dezorientują debugger
- Nakładające się lokalizacje (nie pokazane tutaj) dezorientują debuger (robi to bardzo dobrze podczas ustawiania punktu przerwania)
- ????
Drugim, ale również poważnym problemem jest to, że debugger w niektórych przypadkach nie łamie / nie trafia punktów przerwania.
Jedynym miejscem, w którym mogę sprawić, by debugger działał poprawnie (i konsekwentnie), jest punkt wejścia metody.
Sytuacja trochę się poprawia, gdy IgnoreSymbolStoreSequencePoints
nie jest stosowana.
Wniosek
Możliwe, że debugger VS jest po prostu błędny :(
Bibliografia:
Aktualizacja 1:
Mdbg nie działa w przypadku zestawów 64-bitowych. Więc to się skończyło. Nie mam już 32-bitowych maszyn do przetestowania. Aktualizacja: Jestem pewien, że to nie jest duży problem, czy ktoś ma poprawkę? Edycja: Tak, głupi ja, po prostu uruchom mdbg w wierszu polecenia x64 :)
Aktualizacja 2:
Utworzyłem aplikację C # i próbowałem przeanalizować informacje o wierszu.
Moje ustalenia:
- Po każdej
brXXX
instrukcji musisz mieć punkt sekwencji (jeśli nie jest poprawny, czyli „#line hidden”, wyemituj anop
). - Przed jakąkolwiek
brXXX
instrukcją wyemituj '#line hidden' i anop
.
Zastosowanie tego nie rozwiązuje jednak problemów (samo?).
Ale dodanie następujących daje pożądany efekt :)
- Następnie
ret
wyemituj '#line hidden' i anop
.
Używa trybu, w którym IgnoreSymbolStoreSequencePoints
nie jest stosowany. Po zastosowaniu niektóre kroki są nadal pomijane :(
Oto wyjście IL, gdy zastosowano powyższe:
.method public static object '::baz'(object x) cil managed
{
// Code size 63 (0x3f)
.maxstack 6
.line 15,15 : 1,2 ''
IL_0000: nop
.line 17,17 : 6,15 ''
IL_0001: ldarg.0
.line 16707566,16707566 : 0,0 ''
IL_0002: nop
IL_0003: brtrue IL_000c
.line 16707566,16707566 : 0,0 ''
IL_0008: nop
.line 18,18 : 7,8 ''
IL_0009: ldarg.0
IL_000a: ret
.line 16707566,16707566 : 0,0 ''
IL_000b: nop
.line 19,19 : 6,15 ''
.line 19,19 : 6,15 ''
IL_000c: ldarg.0
IL_000d: isinst [IronScheme]IronScheme.Runtime.Cons
IL_0012: ldnull
IL_0013: cgt.un
.line 16707566,16707566 : 0,0 ''
IL_0015: nop
IL_0016: brfalse IL_0026
.line 16707566,16707566 : 0,0 ''
IL_001b: nop
IL_001c: ldarg.0
.line 20,20 : 7,14 ''
IL_001d: tail.
IL_001f: call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
IL_0024: ret
.line 16707566,16707566 : 0,0 ''
IL_0025: nop
IL_0026: ldsfld object
[Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
IL_002b: ldstr "nooo"
IL_0030: ldarg.0
IL_0031: call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
.line 22,22 : 7,40 ''
IL_0036: tail.
IL_0038: call object [ironscheme.boot]#::
'ironscheme.exceptions::assertion-violation+'(object,object,object)
IL_003d: ret
.line 16707566,16707566 : 0,0 ''
IL_003e: nop
} // end of method 'eval-core(033)'::'::baz'
Aktualizacja 3:
Problem z powyższym „semi-fix”. Peverify zgłasza błędy we wszystkich metodach z powodu nop
po ret
. Naprawdę nie rozumiem problemu. Jak można przeprowadzić nop
weryfikację przerwy po ret
. To jest jak martwy kod (poza tym, że NIE jest to nawet kod) ... No cóż, eksperymenty trwają.
Aktualizacja 4:
Po powrocie do domu usunąłem „nieweryfikowalny” kod, działając na VS2008 i jest znacznie gorzej. Być może rozwiązaniem może być uruchomienie nieweryfikowalnego kodu w celu prawidłowego debugowania. W trybie „zwolnienia” wszystkie dane wyjściowe nadal byłyby weryfikowalne.
Aktualizacja 5:
Teraz zdecydowałem, że mój powyższy pomysł jest na razie jedyną realną opcją. Chociaż wygenerowany kod jest nieweryfikowalny, nie znalazłem jeszcze żadnego VerificationException
. Nie wiem, jaki wpływ będzie miał ten scenariusz na użytkownika końcowego.
Jako bonus, moja druga kwestia również została rozwiązana. :)
Oto mały screencast z tego, z czym skończyłem. Uderza w punkty przerwania, wykonuje odpowiednie kroki (in / out / over), itp. W sumie pożądany efekt.
Ja jednak nadal nie akceptuję tego jako sposobu na zrobienie tego. Wydaje mi się to zbyt hakerskie. Przydałoby się potwierdzenie prawdziwego problemu.
Aktualizacja 6:
Właśnie miałem zmianę, aby przetestować kod na VS2010, wydaje się, że są pewne problemy:
Pierwsza rozmowa teraz nie działa poprawnie. (assertion-violation ...) zostaje trafiony. Inne przypadki działają dobrze.Jakiś stary kod emitował niepotrzebne pozycje. Usunięto kod, działa zgodnie z oczekiwaniami. :)- Co ważniejsze, punkty przerwania kończą się niepowodzeniem przy drugim wywołaniu programu (przy użyciu kompilacji w pamięci, zrzucanie zestawu do pliku wydaje się ponownie uszczęśliwiać punkty przerwania).
Oba te przypadki działają poprawnie pod VS2008. Główną różnicą jest to, że w VS2010 cała aplikacja jest kompilowana dla .NET 4, a pod VS2008, kompiluje się do .NET 2. Obie działają w wersji 64-bitowej.
Aktualizacja 7:
Jak wspomniano, mam mdbg działający pod 64-bitowym. Niestety, ma również problem z punktem przerwania, w którym nie działa, jeśli ponownie uruchomię program (oznacza to, że zostanie ponownie skompilowany, więc nie używa tego samego zestawu, ale nadal używa tego samego źródła).
Aktualizacja 8:
Mam złożony błąd na stronie MS Połącz dotyczące kwestii punktu przerwania.
Aktualizacja: naprawiona
Aktualizacja 9:
Po dłuższym zastanowieniu jedynym sposobem na uszczęśliwienie debugera wydaje się być SSA, więc każdy krok może być izolowany i sekwencyjny. Jeszcze nie udowodniłem tego poglądu. Ale wydaje się to logiczne. Oczywiście, wyczyszczenie temps z SSA przerwie debugowanie, ale jest to łatwe do przełączenia, a pozostawienie ich nie wiąże się z dużym obciążeniem.
nop
s, stepping nie powiedzie się (sprawdzę to ponownie dla pewności). Sądzę, że to ofiara, którą musiałbym złożyć. To nie tak, że VS może działać nawet bez uprawnień administratora :) Przy okazji używając Reflection.Emit przez DLR (bardzo zhakowany wczesny rozgałęziony).