Wspominałem ostatnio o OData scam. Problemie na jaki natrafiłem w WebAPI po tym jak już cały projekt został stworzony. Więc tym tutaj się zajmować nie będę. Zaś opiszę proces w jaki doszedłem do tego, co było nie tak i w jakiej bibliotece to było nie tak – czyli jak za pomocą Visual Studio zdebuggowaliśmy kod i znaleźliśmy przyczynę.

OData składa się z kilku paczek nuget, w zależności od wykorzystania (Web API, WCF, MVC… etc) będzie ich więcej lub mniej. Można oczywiście ściągnąć cały github i mieć kod źródłowy pozwalający zdebuggować nam wszystko. Jednak nie jest to takie łatwe. Sadzę, że postawienie całego środowiska tak by te wszystkie projekty się kompilowały a potem jeszcze by Visual Studio wiedział o symbolach PBP zajęła by ponad dzień.

Kiedyś jedynie co mogliśmy zrobić to po prostu przejrzeć czyiś kod i szukać przyczyny w ten sposób. To nie było wydajne. Problem mógł być na początku drogi jak i w połowie czy pod sam jej koniec. Nie licząc, że za notatki jaka metoda co wywołuje byliśmy odpowiedzialni my :) Na szczęście te czasy minęły .

Teraz wszystko co potrzebujemy to trzy rzeczy:

  1. Plik PDB
  2. Visual Studio
  3. Coś co nam pozwoli z dekompilować kod źródłowy (.NET Refactor, Resharper etc)
Side note: Dobra istnieje jeszcze jedna opcja, windbg… ale serio? No jak już innego wyjścia nie ma to tak :)

Narzędzia generujące PDB z biblioteki

PDB to plik symboli, on określa jakie metody gdzie są dostępne, jak się nazywają itp. Taka ściągawka umożliwiająca aplikacjom jak Visual Studio czy też .NET Framework podać nam więcej informacji na temat biblioteki. Na przykład przy exception dostaniemy linijkę w pliku w którym ten problem wystąpił.

Czasami taki plik jest dostępny publicznie i nie ma problemu z nim, warto sprawdzić czy może a nóż nie istnieje on w katalogu paczek od nuget, a może gdzieś na sieci jest? Na przykład pliki symboli .NET Framework są powszechnie dostępne, dzięki czemu jak chcemy możemy debugować .NET Framework.

Jak go zaś nie ma to musimy się posilać aplikacjami firm trzecich by taki plik wygenerować.

Side note: Sposobów jest kilka nie każdy jest tak wydajny jak inny. Ale w zależności od dostępnych narzędzi to możecie wykorzystać JustCode, PDB Restorer, .NET Reflector  czy też ildasm i ilasm – jednak te dwa ostatnie działają na kodzie IL. Jest jeszcze Resharper, a o nim opowiem gdyż go wykorzystamy.

Jednym ze sposobów jest skorzystanie z resharpera. Jeżeli mamy nasz projekt i zajrzymy do jego referencji, to na bibliotece dla której chcemy wygenerować PDB wystarczy kliknąć PPM i dodać ją do Assembly Explorer:

View in Assembly Explorer
View in Assembly Explorer

Następnie w Assembly Explorer, możemy tą bibliotekę zaznaczyć i wybrać dla niej generowanie PDB:

Generowanie PDB
Generowanie PDB
Progres generowania PDBP
Progres generowania PDBP
Side note: Warto tutaj zaznaczyć, że folder który dodajemy (E:\cache), będzie nam potrzebny później, więc warto by na dysku na którym on jest określony było trochę miejsca (przynajmniej 2-3mb).

Po takiej operacji Resharper wygeneruje nam niezbędne pliki za nas. Warto tutaj zaznaczyć, iż jest to robione za pomocą dekompilacji a to znaczy, że nie wszystko musi tutaj być tak piękne jak w normalnym kodzie. Co za tym idzie… może i będzie miało to wpływ na jakoś naszego debugowania – linijka w jedną, linijka w drugą stronę. Jednak mamy plik PDB.

Visual Studio

Tutaj też musimy zrobić kilka rzeczy! :) Po pierwsze, w oknie przy generowaniu PDB podaliśmy folder w którym będziemy przetrzymywać wygenerowane symbole. To na razie wie tylko i wyłącznie Resharper. Musimy o tym poinformować VS. W tym celu otwieramy Tools | Options | Debugging | Symbols i w polu Cache symbols in this directory wybieramy podany przez nas katalog:

Okno wyboru miejsca cache
Okno wyboru miejsca cache

To jest dość ważne, bez tego, Visual Studio nie będzie wiedział skąd ma pobrać symbole dla bibliotek które chcemy debugować.  Jeżeli zaznaczymy Microsoft Symbol Servers to VS zaciągnie nam z serwera MSowego listę wszystkich PDB dostępnych dla bibliotek Microsoft. Co jest fajne i nie jest. Bo jak debugujemy kod z opcją zewnętrznych bibliotek to MSowe PDB mogą nam nieźle obciążyć system – jak i ogóle spowolnić uruchomienie debugowania kodu.

Side note: jest jeszcze opcja podania tego ręcznie w oknie Modules (dostępne jedynie w trakcie debugowania: Debug | Windows | Modules). Wystarczy, że znajdziemy interesujący nas moduł/bibliotekę i za pomocą PPM myszy wybierzemy Load Symbols.

Mając symbole, musimy jeszcze zmienić ustawienia debugowania, tak by Visual Studio pozwalał nam na debugowanie nie naszego kodu. Możemy to ustawić w oknie Tools | Options | Debugging | General – należy odhaczyć trzeba Enable Just My Code:

Odhaczenie Just My Code
Odhaczenie Just My Code

Teraz mamy już wszystko by rozpocząć debugowanie kodu.

Debuggowanie kodu

Do tego będziemy już potrzebować coś co nam pozwoli dekompilować kod z poziomu VS. Podobnie jak z PDB, możemy do tego wykorzystać Resharpera. Nie mając możliwości dekompilowania kodu, nie uda nam się wejść w metody gdyż MS tego nie wspiera z poziomu VS (albo ja czegoś nie wiem).

Wystarczy, że otworzymy naszą klasę która nas interesuje i wstawimy tam breakpoint i odpalimy debugowanie:

Debuggowanie
Debuggowanie
Debuggowanie
Debuggowanie

To wszystko. Teraz trzeba mieć tylko cierpliwość i ustawiać dość często breakpointy. Dzieje się tak gdyż nie mamy gwarancji, że dana linijka w zdekompilowanym kodzie jest taka sama jak w oryginalnym kodzie. Dla przykładu:

W zdekompilowanym kodzie:

public static double? Distance(this Geometry operand1, Geometry operand2)
{
    return GeometryOperationsExtensions.OperationsFor(operand1, operand2).IfValidReturningNullable<SpatialOperations, double>((Func<SpatialOperations, double>) (ops => ops.Distance(operand1, operand2)));
}

// inny plik
public static readonly MethodInfo FloorOfDouble = ClrCanonicalFunctions.MethodOf<double>((Expression<Func<object, double>>) (_ => Math.Floor(0.0)));

W oryginalnym:

public static double? Distance(this Geometry operand1, Geometry operand2)
{
    return OperationsFor(operand1, operand2).IfValidReturningNullable(ops => ops.Distance(operand1, operand2));
}

// inny plik:

// deklaracja zmiennych
public static readonly MethodInfo FloorOfDouble;

//w konstruktorze
FloorOfDouble = MethodOf(_ => Math.Floor(default(double)));

Podsumowanie

Jak widać z odpowiednimi narzędziami jesteśmy dość szybko dojść do tego co jest nie tak, co nie działa i dlaczego nam to nie działa. Szkoda tylko, że wciąż potrzebujemy narzędzi firm trzecich i nie każdy z tego może skorzystać. Przy aktualnym stanie rzeczy, MS powinien w takim momencie dla swoich produktów wprowadzić ściąganie źródeł z github dla swoich produktów. To po prostu było by wyczes.

Tak jesteśmy ograniczeni. Mimo wszystko, myśląc kilka lat wstecz to i tak jest niezły postęp! Daje on możliwość zorientowania się dość szybko czy jest to coś co jesteśmy wstanie naprawić czy to jest coś co jednak musimy zgłosić na github czy jakoś inaczej.

Zdarzyło wam się debugować już kod zewnętrzny? Wiedzieliście o takiej opcji w VS?

11 KOMENTARZE

  1. “OData składa się z kilku paczek nuget, w zależności od wykorzystania (Web API, WCF, MVC… etc) będzie ich więcej lub mnie.” tutaj tylko literówka ale artykuł bardzo dobry.

  2. Resharper -> Options -> External Sources -> tick Allow downloading from remote locations i Decompile Methods, polecam, nigdy nie musiałem generować pdb, Resharper sam sobie dociąga/generuje potrzebne pliki w momencie pierwszego F12 w daną metodę z biblioteki zewnętrznej :)

  3. Z jakiej wersji R# korzystasz, w mojej 8 nie znalazłem w menu kontekstowym tej opcji z Assembly Explorerem ?

    • sorki, jakoś mi przepadł ten komentarz :( to jest jakaś jedna z nowszych wersji, ale opcja jest dostępna na pewno od ReSharper 10.

Comments are closed.