W codziennej pracy programisty spotykamy się często z typowym stwierdzeniem "A u mnie działa!". Zazwyczaj w takim momencie jesteśmy świadkami niebanalnego problemu, nienadającego się do diagnozy z użyciem Visual Studio, a im bliżej środowiska produkcyjnego dany problem wystąpił tym sytuacja jest bardziej dramatyczna. Celem sesji jest wyposażenie słuchaczy, programistów .NET, w nową (starą) broń do walki z tego typu zdarzeniami.
4Developers: Marcin Iwanowski- WinDbg, gdy Visual Studio to za mało...
1. KONKURS (a właściwie 2)
1. Wejdź na stronę bottega.com.pl/4dev
2.a Stwórz mema
- możesz stworzyć dowolną ilość memów
- wybierając jedną z 3 kategorii:
2.b Głosuj na śmieszne memy
- Azure na node.js zlicza punkty
na potrzeby 3.a
- Twój mózg w tle wydziela hormony
peptydowe (endorfiny)
3.a Wygraj jeden z trzech modeli drona 3.b Wylosuj wejściówkę na szkolenie
Domain Driven Design
11-13.05.16 Lublin
prowadzący: Sławomir Sobótka
4. Rozdanie nagórd (tylko dla obecnych) o 17:55 w sali Eksperci dla praktyków
5. w ciągu (niecałej) godziny
(za) szybkie wprowadzenie do WinDbg
ExploitMe
wyjątek na produkcji
post-mortem debugging
hang
6. jak debugujemy nasze aplikacje?
gdy możemy, podpinamy Visual Studio
a gdy nie możemy?
dyskusja na temat debugowania środowisk
testowych oraz produkcyjnych
a jak debugujemy nie nasz kod?
18. problem 1: wyjątek na produkcji
pewnego pięknego poniedziałku... nasza
solucja NerdDinner załapała buga w search’u
pod adresem /home/MobileIndex
polecenie od szefa: zdiagnozuj problem,
odpowiedz na pytanie „czy można problem
rozwiązać bez wdrażania nowej wersji?”,
jeżeli tak to rozwiąż go
UWAGA: ludzie z IT na produkcji pozwolili
nam używać jedynie WinDbg
19. problem 2: post mortem debugging
problem identyczny jak na poprzednim
slajdzie
„ale zaraz zaraz jak to zatrzymać portal
na produkcji?”
20. problem 3: hang
scenariusz 1: po zintegrowaniu StyleCopa
z projektem plan na bamboo zawiesza się
24. KONKURS (a właściwie 2)
1. Wejdź na stronę bottega.com.pl/4dev
2.a Stwórz mema
- możesz stworzyć dowolną ilość memów
- wybierając jedną z 3 kategorii:
2.b Głosuj na śmieszne memy
- Azure na node.js zlicza punkty
na potrzeby 3.a
- Twój mózg w tle wydziela hormony
peptydowe (endorfiny)
3.a Wygraj jeden z trzech modeli drona 3.b Wylosuj wejściówkę na szkolenie
Domain Driven Design
11-13.05.16 Lublin
prowadzący: Sławomir Sobótka
4. Rozdanie nagórd (tylko dla obecnych) o 17:55 w sali Eksperci dla praktyków
Editor's Notes
dotpeeka włączyć przed prezentacją
solucję asp.net web odpalić wcześniej
otworzyć C:\Users\Marcinii\Desktop\Bamboo\SampleApp
15 minut - zajęło mi demo z pierwszą aplikacją i wstęp
10 minut - demo z debugowaniem problemu na produkcji
5 minut - post mortem
10 minut - stylecop
5 minut - hang 100%
5 minut - memory leak
Oraz głównie programista Sharepoint a właściwie to człowiek od rozwiązywania problemów w aplikacji .NET
Chciałbym dziś Wam przybliżyć trochę faktycznie moją pracę
rozgrzewka – żebyśmy przestawili się w tryb debugowania
zapisywać wszystko co powiedzą
używamy Console.WriteLine, albo MessageBox
logujemy
co robimy na śodowisku testowym?
próbujemy odtworzyć problem u siebie,
jak możemy kopiujemy dane do siebie
możemy wdrożyć bibliotekę z większym poziomem logowania, albo przełączyć na większy poziom debugowania
klikamy ile się da żeby odkryć przyczynę problemu
co robimy na środowisku produkcyjnym?
monitoring standardowych logów
czasem wolno nam włączyć bardziej szczegółowe logowanie
zwykle nie możemy klikać
próbujemy odtworzyć problem na innych maszynach
kopiujemy dane do siebie (o ile możemy)
tak naprawdę większość naszych działań zmierzają na początku do tego:
a później zaczyna się główkowanie
a tak naprawdę sednem jest to czy używamy odpowiednich narzędzi do diagnozy problemu
szukamy stacktrace -> jak już wiemy to przyglądamy się tak długo aż na coś wpadniemy -> możemy chcieć podglądać zawartość pamięci
pierwsze podstawowe narzędzia i jedne z najbardziej potęznych narzędzie do starcza nam Microsoft
https://msdn.microsoft.com/en-us/windows/hardware/hh852365
http://rxwen.blogspot.com/2010/04/standalone-windbg-v6120002633.html
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86
Dodatkowo dodatki: SOS i SOSEX
a teraz kilka słów o komendach które są dość często używane w tym narzędziu
class Simple
{
static void Main(string[] args)
{
System.Console.WriteLine("Welcome to Advanced .NET Debugging!");
System.Console.WriteLine("Press any key to exit");
System.Console.ReadKey();
}
}
ntsd
ntsd [path]
symbol path nie został ustawiony (o tym zaraz)
wylistowane załadowane moduły
break instruction excpetion – zawsze gdy podpinamy debugger jest zatrzymuje on dzaiałanie aplikacji
i użytkownik może coś teraz zrobić
target – zwykle 0
a co gdy mamy już uruchomiony proces?
skąd PID procesu mieć? tlist
tym razem dużó więcej modułów zostało załadowanych
można pokazać cdb
najpierw .reload
uruchomić .symfix i .reload i tym razem wszystkie dll’ki zostały załadowane poprawnie
.pdb
pomocne także w przypadku aplikacji .NET
publiczne i prywatne
zademonstrować: ctrl + c i g
ntsd –g [path] uruchomi aplikację bez zatrzymania
instrukcje do poniższego:
.symfix
.reload
.g
Press any key
CTRL+C
.loadby sos clr
!name2ee Breakpoint3.exe Breakpoint3.Breakpoint.AddAndPrint
szukamy JITTED code Address
bp JITTED code address
g
g
eip pokazuje w jakiej funkcji jesteśmy/ w jakiej linijce
u – pokazuje kilka następnych linii
t wchodzi w call
p nie wchodzi w call
pusta komenda powtarza
pc do call’a
pt do końca funkcji
class Breakpoint
{
static void Main(string[] args)
{
Console.WriteLine("Press any key (1st instance function)");
Console.ReadKey();
Breakpoint bp = new Breakpoint();
bp.AddAndPrint(10, 5);
Console.WriteLine("Press any key (2nd instance function)");
Console.ReadKey();
bp = new Breakpoint();
bp.AddAndPrint(100, 50);
Console.WriteLine("Press any key (static function)");
Console.ReadKey();
AddAndPrintStatic(20, 15);
}
public void AddAndPrint(int a, int b)
{
int res = a + b;
Console.WriteLine("Adding {0}+{1}={2}", a, b, res);
}
public static void AddAndPrintStatic(int a, int b)
{
int res = a + b;
Console.WriteLine("Adding {0}+{1}={2}", a, b, res);
}
}
.load – trzeba znaleźć odpowiednią wersję - można pokazać, trzeba dodatkowo / robić
dlaczego sos jest w różnych wersjach, bo implementujesz szczeóły które różnią się w każdym CLR żeby móc go debugować
ale lepszy jest loadby
NIE WCHODZIĆ W SZCZEGÓŁY
bp – niby proste, zacznijmy od notepada w .NET jest troszkę trudniej
ntsd notepad
.symfix
.reload
X notepad!*Save*
szukamy adres SaveFile
bp notepad!SaveFile
g
i zachować jakiś plik
teraz jeszcze raz z brekpointem demo
!name2ee – potrafi sprawdzić czy funkcja już zostałą z jitowana
.loadby sos clr
!name2ee Breakpoint3.exe Breakpoint3.Breakpoint.AddAndPrint
szukamy JITTED code Address
!U adresmetody
postawić breakpointa
bp [address]
i jak breakpoint uderzy
to !ClrStack
!bpmd Breakpoint3.exe Breakpoint3.Breakpoint.AddAndPrint
a teraz demo kiedy funkcja nie jest zjitowana
włączyć do pierwszej spacji
ctrl + c
!name2ee Breakpoint3.exe Breakpoint3.Breakpoint.AddAndPrint
pokazać że jest not jitted
!bpmd -md 00844ce0
stawia breakpointa
!mx 2.2.KontrolaWykonania!*Add*
!mbm 2.2.KontrolaWykonania!*Add*
kiedyś też można było się dowiedzieć o adresie ale trzeba było postawić breakpoint w samym kodzie jita który zapisywał adres funkcji
a co to jest NGEN?
popatrzmy na ścieżkę podczas ładowania modułów
%windir%\assembly\NativeImages_v2.0.50727_<architecture>
tworzenie obrazu:
%windir%\microsoft.net\framework\v2.0.50727\ngen install 03ObjTypes.exe
debugowanie niczym się nie różni, metody już będą skompilowane przez JIT
!bpmd 03ObjTypes.exe Advanced.NET.Debugging.Chapter3.Comparer`1.GreaterThan
!bpmd 03ObjTypes.exe Advanced.NET.Debugging.Chapter3.Comparer`2.GreaterThan – dwa typy generyczne
public class ObjTypes
{
public struct Coordinate
{
public int xCord;
public int yCord;
public int zCord;
public Coordinate(int x, int y, int z)
{
xCord = x;
yCord = y;
zCord = z;
}
}
private Coordinate coordinate;
int[] intArray = new int[] { 1, 2, 3, 4, 5 };
string[] strArray = new string[] {"Welcome",
"to",
"Advanced",
".NET",
"Debugging"};
static void Main(string[] args)
{
Coordinate point = new Coordinate(100, 100, 100);
Console.WriteLine("Press any key to continue (AddCoordinate)");
Console.ReadKey();
ObjTypes ob = new ObjTypes();
ob.AddCoordinate(point);
Console.WriteLine("Press any key to continue (Arrays)");
Console.ReadKey();
ob.PrintArrays();
Console.WriteLine("Press any key to continue (Generics)");
Console.ReadKey();
Comparer<int> c = new Comparer<int>();
Console.WriteLine("Greater {0}", c.GreaterThan(5, 10));
Console.WriteLine("Press any key to continue (Exception)");
Console.ReadKey();
ob.ThrowException(null);
}
public void AddCoordinate(Coordinate coord)
{
coordinate.xCord += coord.xCord;
coordinate.yCord += coord.yCord;
coordinate.zCord += coord.zCord;
Console.WriteLine("x:{0}, y:{1}, z:{2}",
coordinate.xCord,
coordinate.yCord,
coordinate.xCord);
}
public void PrintArrays()
{
foreach (int i in intArray)
{
Console.WriteLine("Int: {0}", i);
}
foreach (string s in strArray)
{
Console.WriteLine("Str: {0}", s);
}
}
public void ThrowException(ObjTypes obj)
{
if (obj == null)
{
throw new System.ArgumentException("Obj cannot be null");
}
}
}
public class Comparer<T> where T : IComparable
{
public T GreaterThan(T d, T d2)
{
int ret = d.CompareTo(d2);
if (ret > 0)
return d;
else
return d2;
}
public T LessThan(T d, T d2)
{
int ret = d.CompareTo(d2);
if (ret < 0)
return d;
else
return d2;
}
}
ntsd aplikacja
.symfix
.reload
g
ctrl+c
.loadby sos clr
!ClrStack
pokazuje się unable bo potrzeba zmienić wątek, nie zatrzymaliśmy się w wątku natywnym
~0s
!ClrStack już działa
!ClrStack –a wyświetla szczegóły parametrów i lokalnych rzeczy
po pierwsze musimy się dowiedzieć czy zmienna jest klasą czy strukturą
!DumpObj [adres]
widać że to nie obiekt,
r – pokaże rejestrj esp (stack pointer) – widać że zawie podobny adres do naszego więc nasza zmienna jest na stosie
więc robimy
d [adres] – widać wartości 100
DEMO2:
!bpmd ObjTypes3.exe ObjTypes3.ObjTypes.AddCoordinate
g
g
!DumpObj [adres thisa] – widać jakie pola ma obiekt
Metadata Table – opisuje jak wygląda dany obiekt, Field 4000 0001 (to jest pole i offset tego pola w MT)
VT – 1 to oznacza że to jest ValueType
dd 02e34e98+0xc – adres obiektu plus offset żeby zobaczyć wartość pola
albo lepiej tak:
!DumpVC 0x010a4dac 0x02e34ea4 – pierwszy adres to adres memory table, a drugi adres to value danego obiektu
widać ładnie nazwy zmiennych
DEMO3:
g
do arrays
!do drugiego localsa z maina (ObjTypes)
teraz możemy zrobić !DumpObj intArray lub d adres intarray – zwrócić uwagę że ilość elementów jest zapisana w drugim sektorze
!DumpObj strArray
!DumpObj –nofields pierwszego elementu
na szczęście jest !DumpArray
!DumpArray –details intArray lub strArray
DEMO4:
Pamiętacie żeby odnaleźć obiektu musiałem zrobić !ClrStack –a a później znaleźć localsa odpowiedniego
Ale można prościej używająć !DumpStackObjects (można ograniczyć pamięć)
wykonać:
!DumpStackObjects
!DumpObj adres sometype
pokazać jeszcze że !DumpObj size jest różny od !ObjSize na przykładzie ObjTypes
Kod:
public class ObjTypes
{
public struct Coordinate
{
public int xCord;
public int yCord;
public int zCord;
public Coordinate(int x, int y, int z)
{
xCord = x;
yCord = y;
zCord = z;
}
}
private Coordinate coordinate;
int[] intArray = new int[] { 1, 2, 3, 4, 5 };
string[] strArray = new string[] {"Welcome",
"to",
"Advanced",
".NET",
"Debugging"};
static void Main(string[] args)
{
Coordinate point = new Coordinate(100, 100, 100);
Console.WriteLine("Press any key to continue (AddCoordinate)");
Console.ReadKey();
ObjTypes ob = new ObjTypes();
ob.AddCoordinate(point);
Console.WriteLine("Press any key to continue (Arrays)");
Console.ReadKey();
ob.PrintArrays();
Console.WriteLine("Press any key to continue (Generics)");
Console.ReadKey();
Comparer<int> c = new Comparer<int>();
Console.WriteLine("Greater {0}", c.GreaterThan(5, 10));
Console.WriteLine("Press any key to continue (Exception)");
Console.ReadKey();
ob.ThrowException(null);
}
public void AddCoordinate(Coordinate coord)
{
coordinate.xCord += coord.xCord;
coordinate.yCord += coord.yCord;
coordinate.zCord += coord.zCord;
Console.WriteLine("x:{0}, y:{1}, z:{2}",
coordinate.xCord,
coordinate.yCord,
coordinate.xCord);
}
public void PrintArrays()
{
foreach (int i in intArray)
{
Console.WriteLine("Int: {0}", i);
}
foreach (string s in strArray)
{
Console.WriteLine("Str: {0}", s);
}
}
public void ThrowException(ObjTypes obj)
{
if (obj == null)
{
throw new System.ArgumentException("Obj cannot be null");
}
}
}
public class Comparer<T> where T : IComparable
{
public T GreaterThan(T d, T d2)
{
int ret = d.CompareTo(d2);
if (ret > 0)
return d;
else
return d2;
}
public T LessThan(T d, T d2)
{
int ret = d.CompareTo(d2);
if (ret < 0)
return d;
else
return d2;
}
}
uruchomić aplikację ntsd ObjTypes3.exe
nie pauzować do momentu rzucenia wyjątku
widzimy ładnie co zostało rzucone
jak chcemy podejrzeć szczegóły to:
!Threads
!do ades wyjątku
!do –nofields adres pola _message
albo prościej !pe
!DumpStack -EE
!U adres funkcji
!DumpIL method descriptor
bp na adres instrukcji cmp
czekamy na bp
!ClrStack -a
szukamy adresu lokalnej zmiennej z license is valid
przez okno memory zmieniamy wartosc
g
rozwiązanie:
sxe clr
!ClrStack
!U /d adres metody
bp w miejscu gdzie result będzie już wypełniony
!ClrStack –a i brak danych o tej zmiennej, bo włączone są optymalizacje w release, ale !dso już działa
result przeczytać w okienku memory
http://www.geonames.org/login
i znów włączyć, znów błąd ale inny
na http://www.geonames.org/ w profilu trzeba pozwolić na web service
username=marcinii&
i bingo - naprawione
w tej aplikacji występuje jeszcze jeden problem – brak folderu Logs
można porozmawiać o Debug/Release
jak ułatwić diagnozowanie tego typu problemów?
więcej logów
adplus skonfigurowany na robienie dumpa, adplus częścią narzędzie do debugowania
https://msdn.microsoft.com/en-us/library/windows/hardware/ff537953(v=vs.85).aspx
zamiast sxe clr można użyć soe
!soe -Create System.Data.SqlClient.SqlException
.dump /mf [path]
Task Manager
Hang może być różny, najczęsciej:
aplikacja faktycznie coś robi
aplikacja robi coś w kółko
deadlock
C:\Users\Marcinii\Desktop\Bamboo\SampleApp
SampleApp
uwaga na wersję MSBuild, trzeba zmienić w .bat
po uruchomieniu i zatrzymaniu w momenciu zwisu:
!DumpStack –EE
szukamy StyleCop
przełączamy się na wątek
!ClrStack
odnajdujemy metodę która przekazuje nazwę folderu – sprawdzić tą nazwę
nie wolno: ruszać aplikacji z folderu Program Files
poza tym wszystko inne dozwolone
zawieszenei aplikacji normalnie bada się przez zrobienie kilku zrzutów typu hang i porównanie ich ze sobą
przydatna komenda !runaway
opisane na blogu msdn:
https://blogs.msdn.microsoft.com/tess/2009/12/21/high-cpu-in-net-app-using-a-static-generic-dictionary/
!DumpHeap –type Person
!DumpHeap /d -mt 00f6aa68
!GCRoot address
raczej rzadko zdarza się klasyczny wyciek pamięci (choć jest możliwy)
to my nie zwolniliśmy jakiegoś obiektu (lub 3rd party)
!do i zobaczyć size
!ObjSize size wszystkich elementów
na koniec na pewno powiedzieć że te problemy nie debuguje się w 15 minut