2008-10-13 19 views
9

Al utilizar la API no administrada para el marco .NET para perfilar un proceso .NET en proceso, es posible buscar el puntero de instrucción IL correlacionado con el puntero de instrucción nativa proporcionado a la función StackSnapshotCallback?Cómo mapea un puntero de instrucción nativa a IL

Como probablemente sea obvio, estoy tomando una instantánea de la pila actual, y me gustaría proporcionar información sobre el número de archivo y línea en el volcado de la pila. El Managed Stack Explorer lo hace al consultar ISymUnmanagedMethod::GetSequencePoints. Esto es genial, pero los puntos de secuencia están asociados a los desplazamientos, y hasta ahora he supuesto que estos son desplazamientos desde el comienzo del método (en lenguaje intermedio).

En un comentario de seguimiento a su publicación de blog Profiler stack walking: Basics and beyond, David Broman indica que este mapeo se puede lograr usando ICorDebugCode::GetILToNativeMapping. Sin embargo, esto no es ideal, ya que para obtener esta interfaz es necesario adjuntar a mi proceso otro proceso de depurador.

Me gustaría evitar ese paso porque me gustaría seguir siendo capaz de ejecutar mi aplicación desde el depurador de Visual Studio mientras tomo estas instantáneas. Hace que sea más fácil hacer clic en el número de línea en la ventana de salida y acceder al código en cuestión.

La funcionalidad es posible ... puede escupir un rastro de pila numerado en línea dentro del código administrado, la única pregunta, es accesible. Además, no quiero usar la funcionalidad System::Diagnostics::StackTrace o System::Environment::StackTrace porque, por motivos de rendimiento, necesito retrasar el vaciado real de la pila ... por lo que es deseable guardar el costo de resolución de los nombres de los métodos y la ubicación del código para más tarde. ... junto con la capacidad de entremezclar marcos nativos y gestionados.

Respuesta

5

Con el fin de traducir de un puntero de instrucciones nativas a lo dispuesto por ICorProfilerInfo2::DoStackSnapshot a un método de lenguaje intermedio de desplazamiento, hay que dar dos pasos desde DoStackSnapshot proporciona un puntero de instrucción FunctionID y nativa como una dirección de memoria virtual.

El paso 1 consiste en convertir el puntero de instrucción en un desplazamiento de método de código nativo. (un desplazamiento desde el comienzo del método JITed). Esto se puede hacer con 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; 
} 

Paso 2. Una vez que haya un desplazamiento desde el principio del método de código natvie, puede utilizar esto para convertir a un desplazamiento desde el principio del método de lenguaje intermedio usando 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; 
} 

Esto luego puede usarse para mapear la ubicación de código a un número de expediente y la línea de uso de la API de símbolos

Gracias a Mithun Shanbhag de dirección en la búsqueda de la solución.

+0

Pequeño error aquí - frame.pc en la línea 17 del primer fragmento de código debe ser "instructionPtr", que es el parámetro UINT_PTR ip de su devolución de llamada DoStackSnapshot. ¡Gracias, amigo Steven! Realmente me ayudaste :) –

+0

Hrm ... buen punto. No recuerdo de dónde salió frame.pc. Tal vez estaba tratando de dejar en claro lo que se estaba utilizando. En cualquier caso, el valor real con un comentario acerca de dónde viene parece prudente. Editado Gracias por el puntero. – Steven

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

Asegúrate de que tu compilación genere símbolos.

Ampliando la discusión:

Como es obvio, probablemente, estoy tomando una instantánea de la pila actual, y me gustaría proporcionar información de archivo y número de línea en el volcado de pila.

Teniendo en cuenta esto - Parece que la única razón por la que no está adjuntando al proceso es para que pueda depurar su herramienta, o partes de él, con facilidad, ya que estás desarrollarla. Esa IMO es una excusa pobre para no elegir un mejor diseño (ICorDebug o w/e) cuando esté disponible. La razón de su diseño pobre es porque su código se ejecuta en el espacio de proceso de (presumiblemente) binarios externos, causando efectos secundarios desagradables ('a veces' raros) (incluso corromper datos de otra persona) en estados de proceso corruptos conocidos (o peor aún desconocidos). Eso debería ser suficiente para empezar, pero incluso de lo contrario, hay varios casos de borde con código de subprocesos múltiples, etc. en los que el diseño debe solucionarse.

La mayoría de las personas generalmente pregunta "¿Qué estás realmente tratando de hacer?" como respuesta a una forma abiertamente compleja de hacer las cosas. En la mayoría de las cajas hay una manera más simple/más fácil. Después de haber escrito un rastreador de pila para el código nativo, sé que puede ser complicado.

Ahora tal vez podría terminar haciendo que todo funcione, por lo tanto, solo mi $.02

+0

Gracias por la sugerencia, pero la pregunta indica que me gustaría retrasar la resolución de los símbolos y poder resolver los marcos no administrados. Este enfoque no cumple ninguno de los requisitos. He editado la pregunta para que sea un poco más explícita. – Steven

+0

Si lo está ejecutando en un depurador, es probable que la ventana 'pila de llamadas' ya esté resolviendo símbolos. Marcos no administrados: la pila nativa IMO ya está muy bien definida y ya existen funciones de API Win32 para hacerlo. P.ej. StackWalk64, SymGetLineFromAddr64 y SymGetModuleBase64. –

+0

Estoy modificando una herramienta de detección de fugas para mostrar pilas en modo mixto y no quiero resolver realmente todas las pilas. Solo aquellos que conducen a filtraciones. No quiero romper el depurador en cada asignación. Y las funciones dbghlp que describes solo funcionan en pilas nativas puras. – Steven

Cuestiones relacionadas