2008-10-13 6 views
9

Podczas korzystania z niezarządzanego API dla platformy .NET do profilowania procesu w procesorze .NET, możliwe jest sprawdzenie wskaźnika instrukcji IL, który koreluje z macierzysty wskaźnik instrukcji dostarczany do funkcji StackSnapshotCallback?Jak mapować natywny wskaźnik instrukcji IL w trakcie przetwarzania

Co jest prawdopodobnie oczywiste, robię migawkę bieżącego stosu i chciałbym podać informacje o pliku i numerze linii w zrzucie stosu. Managed Stack Explorer robi to, wysyłając zapytanie do ISymUnmanagedMethod::GetSequencePoints. To jest świetne, ale punkty sekwencji są powiązane z przesunięciami i do tej pory zakładałem, że są to przesunięcia od początku metody (w języku pośrednim).

W komentarzu uzupełniającym do jego wpisu na blogu Profiler stack walking: Basics and beyond, David Broman wskazuje, że mapowanie to można osiągnąć za pomocą ICorDebugCode::GetILToNativeMapping. Jednak nie jest to idealne rozwiązanie, ponieważ uzyskanie tego interfejsu wymaga dołączenia do mojego procesu z innego procesu debuggera.

Chciałbym tego uniknąć, ponieważ chciałbym nadal móc uruchamiać moją aplikację z poziomu debuggera Visual Studio podczas wykonywania tych migawek. Ułatwia kliknięcie numeru linii w oknie wyjściowym i przejście do odpowiedniego kodu.

Funkcjonalność jest możliwa ... możesz wypluć ślad stosu o numerach linii wewnątrz zarządzanego kodu, jedyne pytanie, czy jest dostępne. Ponadto nie chcę używać funkcji System::Diagnostics::StackTrace ani System::Environment::StackTrace, ponieważ ze względu na wydajność muszę opóźnić faktyczny zrzut stosu .... tak więc obniżenie kosztów rozwiązywania nazw metod i położenia kodu na później jest pożądane ... wraz z możliwością wymieszania rodzimych i zarządzanych ramek.

Odpowiedz

5

W celu przełożenia z native wskaźnik instrukcji przewidziane przez ICorProfilerInfo2::DoStackSnapshot do pośredniej metody językowej offsetowego, należy wykonać dwa kroki od DoStackSnapshot zapewnia FunctionID i natywnej obsługi wskaźnika jako wirtualnego adresu pamięci.

Krok 1 polega na przekonwertowaniu wskaźnika instrukcji na korektę metody kodu natywnego. (przesunięcie od początku metody JITed). Można to zrobić z ICorProfilerInfo2::GetCodeInfo2

ULONG32 pcIL(0xffffffff); 
HRESULT hr(E_FAIL); 
COR_PRF_CODE_INFO* codeInfo(NULL); 
COR_DEBUG_IL_TO_NATIVE_MAP* map(NULL); 
ULONG32 cItem(0); 

UINT_PTR nativePCOffset(0xffffffff); 
if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functioId, 0, &cItem, NULL)) && 
    (NULL != (codeInfo = new COR_PRF_CODE_INFO[cItem]))) 
{ 
    if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functionId, cItem, &cItem, codeInfo))) 
    { 
     COR_PRF_CODE_INFO *pCur(codeInfo), *pEnd(codeInfo + cItem); 
     nativePCOffset = 0; 
     for (; pCur < pEnd; pCur++) 
     { 
      // 'ip' is the UINT_PTR passed to the StackSnapshotCallback as named in 
      // the docs I am looking at 
      if ((ip >= pCur->startAddress) && (ip < (pCur->startAddress + pCur->size))) 
      { 
       nativePCOffset += (instructionPtr - pCur->startAddress); 
       break; 
      } 
      else 
      { 
       nativePCOffset += pCur->size; 
      } 

     } 
    } 
    delete[] codeInfo; codeInfo = NULL; 
} 

Krok 2. Po przesunięcie od początku metody kodu natvie, można to wykorzystać do konwersji na przesunięcie od początku metodą z zastosowaniem języka pośredniego ICorProfilerInfo2::GetILToNativeMapping.

if ((nativePCOffset != -1) && 
    SUCCEEDED(hr = pInfo->GetILToNativeMapping(functionId, 0, &cItem, NULL)) && 
    (NULL != (map = new COR_DEBUG_IL_TO_NATIVE_MAP[cItem]))) 
{ 
    if (SUCCEEDED(pInfo->GetILToNativeMapping(functionId, cItem, &cItem, map))) 
    { 
     COR_DEBUG_IL_TO_NATIVE_MAP* mapCurrent = map + (cItem - 1); 
     for (;mapCurrent >= map; mapCurrent--) 
     { 
      if ((mapCurrent->nativeStartOffset <= nativePCOffset) && 
       (mapCurrent->nativeEndOffset > nativePCOffset)) 
      { 
       pcIL = mapCurrent->ilOffset; 
       break; 
      } 
     } 
    } 
    delete[] map; map = NULL; 
} 

ta może być następnie wykorzystana do mapowania lokalizacji kodu do pliku i numer linii używając API symbol

Dzięki Mithun Shanbhag dla kierunku w znalezieniu rozwiązania.

+0

Mały błąd tutaj - frame.pc w linii 17 pierwszego fragmentu kodu powinien być "instructionPtr", który jest parametrem ip UINT_PTR twojego wywołania zwrotnego DoStackSnapshot. Dzięki Steven! Naprawdę mi pomogłeś :) –

+0

Hmm ... dobry punkt. Nie pamiętam, skąd pochodzi frame.pc. Może próbowałem wyjaśnić, co jest używane. W każdym razie rzeczywista wartość z komentarzem o tym, skąd pochodzi, wydaje się być rozważna. Edytowane. Dzięki za wskaźnik. – Steven

0
Console.WriteLine("StackTrace: '{0}'", Environment.StackTrace); 

Upewnij się, że twój build generuje symbole.

Rozszerzając dyskusji:

Jak to chyba oczywiste, Biorę migawkę bieżącego stosu, i chciałby, aby zapewnić informacje o pliku i numer linii w wysypisko stosu.

Biorąc to pod uwagę - Wygląda na to jedyny powód nie jesteś dołączenie do procesu jest tak, że można debugować narzędzia, lub jego części, łatwo, jak jesteś rozwijania go. To, że IMO jest marnym pretekstem do nie wyboru lepszego projektu (ICorDebug lub w/e), gdy jest dostępny. Powodem jego złego projektu jest to, że twój kod wykonuje się w przestrzeni procesowej (prawdopodobnie) zewnętrznych plików binarnych, powodując nieprzyjemne ("czasami" rzadkie) efekty uboczne (w tym niszczenie danych innych osób) w znanych (lub gorszych - nieznanych) uszkodzonych stanach procesu. To powinno wystarczyć, ale w przeciwnym razie istnieje kilka przypadków krawędzi z wielowątkowym kodem, itp., W których projekt musi zostać opracowany.

Większość ludzi zadaje pytanie "Co tak naprawdę chcesz zrobić?" jako odpowiedź na jawnie skomplikowany sposób robienia rzeczy. W przypadku większości przypadków istnieje prostszy/łatwiejszy sposób. Po napisaniu znacznika stosu dla natywnego kodu, wiem, że może stać się brudny.

Teraz może się skończyć, aby wszystko działało, więc - tylko moje $.02

+0

Dzięki za sugestię, ale pytanie mówi, że chciałbym opóźnić rozdzielczość symboli i móc rozwiązywać niezarządzane ramki. To podejście nie spełnia żadnego wymogu. Zmieniłem to pytanie, by było nieco bardziej jednoznaczne. – Steven

+0

Jeśli używasz go w debugerze, najprawdopodobniej okno "stos połączeń" już rozwiązuje symbole. Klatki niezarządzane - stos natywny IMO jest już bardzo dobrze zdefiniowany i istnieją już funkcje interfejsu Win32 API, aby to zrobić. Na przykład. StackWalk64, SymGetLineFromAddr64 i SymGetModuleBase64. –

+0

Modyfikuję narzędzie wykrywające wycieki, aby wyświetlać stosy w trybie mieszanym i nie chcę faktycznie usuwać wszystkich stosów. Tylko te, które prowadzą do przecieków. Nie chcę włamać się do debuggera przy każdej alokacji. Opisane funkcje dbghlp działają tylko na czystych stosach natywnych. – Steven