Как вы сопоставляете собственный указатель инструкций с IL в процессе

Можно ли при использовании неуправляемого API для платформы .NET для профилирования внутрипроцессного процесса .NET найти указатель инструкций IL, который соответствует собственному указателю инструкций, предоставленному функции StackSnapshotCallback?

Как, вероятно, очевидно, я делаю снимок текущего стека и хотел бы предоставить информацию о номере файла и строке в дампе стека. Managed Stack Explorer делает это, запрашивая ISymUnmanagedMethod::GetSequencePoints. Это здорово, но точки последовательности связаны со смещениями, и я до сих пор предполагал, что это смещения с начала метода (на промежуточном языке).

В последующем комментарии к своему сообщению в блоге Обход стека профайлеров: основы и не только, Дэвид Броман указывает, что это сопоставление может быть достигнуто с помощью ICorDebugCode::GetILToNativeMapping. Однако это не идеально, так как для получения этого интерфейса требуется присоединение к моему процессу из другого процесса отладчика.

Я хотел бы избежать этого шага, потому что я хотел бы по-прежнему иметь возможность запускать свое приложение из отладчика Visual Studio, пока я делаю эти снимки. Это упрощает нажатие на номер строки в окне вывода и переход к нужному коду.

Функциональность возможна .... вы можете выплюнуть трассировку стека с номером строки по желанию внутри управляемого кода, единственный вопрос, доступен ли он. Кроме того, я не хочу использовать функциональность System::Diagnostics::StackTrace или System::Environment::StackTrace, потому что по соображениям производительности мне нужно отложить фактический дамп стека... поэтому желательно сохранить затраты на разрешение имен методов и расположение кода на потом. ... наряду с возможностью смешивать собственные и управляемые кадры.


person Steven    schedule 13.10.2008    source источник


Ответы (2)


Чтобы преобразовать собственный указатель инструкций, предоставленный ICorProfilerInfo2::DoStackSnapshot, в смещение метода промежуточного языка, вы должны выполнить два шага, поскольку DoStackSnapshot предоставляет FunctionID и собственный указатель инструкций в качестве адреса виртуальной памяти.

Шаг 1 заключается в преобразовании указателя инструкции в смещение метода собственного кода. (смещение от начала JIT-метода). Это можно сделать с помощью 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;
}

Шаг 2. Получив смещение от начала метода нативного кода, вы можете использовать его для преобразования в смещение от начала метода промежуточного языка с помощью 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;
}

Затем это можно использовать для сопоставления местоположения кода с файлом и номером строки с помощью API-интерфейсов символов.

Благодаря Митхуну Шанбхагу за направление в поиске решения.

person Steven    schedule 01.11.2008
comment
Здесь небольшая ошибка — frame.pc в строке 17 первого фрагмента кода должен быть инструкциямPtr, который является параметром ip UINT_PTR вашего обратного вызова DoStackSnapshot. Большое спасибо, Стивен! Вы меня очень выручили :) - person Omer Raviv; 01.09.2010
comment
Хм... хорошее замечание. Я не помню, откуда взялся frame.pc. Возможно, я пытался сделать очевидным то, что используется. В любом случае фактическое значение с комментарием о том, откуда оно берется, кажется благоразумным. Отредактировано. Спасибо за указатель. - person Steven; 11.10.2010
comment
Шаг 1 и 2 действительно хорошо объяснены. Однако поиск символьного API и его использование кажутся далеко не тривиальными. Не могли бы вы объяснить, как это сделать? - person bbc; 04.09.2018

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

Убедитесь, что ваша сборка генерирует символы.

Расширение обсуждения:

Как, вероятно, очевидно, я делаю снимок текущего стека и хотел бы предоставить информацию о номере файла и строке в дампе стека.

Учитывая это. Похоже, единственная причина, по которой вы не подключаетесь к процессу, заключается в том, что вы можете легко отлаживать свой инструмент или его части во время его разработки. Эта ИМО является плохим оправданием для того, чтобы не выбрать лучший дизайн (ICorDebug или w/e), когда он доступен. Причина его плохого дизайна заключается в том, что ваш код выполняется в пространстве процессов (предположительно) внешних двоичных файлов, вызывая неприятные («иногда» редкие) побочные эффекты (включая повреждение чужих данных) в известных (или, что еще хуже, неизвестных) поврежденных состояниях процесса. Этого должно быть достаточно для начала, но даже в противном случае есть несколько крайних случаев с многопоточным кодом и т. д., где дизайн необходимо обойти.

Большинство людей обычно спрашивают: «Что вы на самом деле пытаетесь сделать?» как ответ на откровенно сложный способ ведения дел. В большинстве случаев есть более простой способ. Написав трассировщик стека для нативного кода, я знаю, что он может запутаться.

Теперь, может быть, вы в конечном итоге заставите все работать, так что - Просто мои 0,02 доллара.

person Community    schedule 17.10.2008
comment
Спасибо за предложение, но в вопросе говорится, что я хотел бы отложить разрешение символов и иметь возможность разрешать неуправляемые кадры. Этот подход не соответствует ни одному из требований. Я отредактировал вопрос, чтобы он был немного более явным. - person Steven; 22.10.2008
comment
Если вы запускаете его в отладчике, скорее всего, окно «стека вызовов» уже разрешает символы. Неуправляемые кадры — собственный стек IMO уже очень хорошо определен, и для этого уже существуют функции Win32 API. Например. StackWalk64, SymGetLineFromAddr64 и SymGetModuleBase64. - person ; 23.10.2008
comment
Я модифицирую инструмент обнаружения утечек для отображения стеков смешанного режима и не хочу на самом деле разрешать все стеки. Только те, которые приводят к утечкам. Я не хочу ломать отладчик при каждом выделении памяти. И функции dbghlp, которые вы описываете, работают только с чистыми нативными стеками. - person Steven; 23.10.2008
comment
То, что вы делаете, требует сотрудничества управляемой и собственной стороны стека и наличия вспомогательных функций на обеих сторонах, записывающих текущее состояние стека на каждом уровне. Очень неуклюжий дизайн, не говоря уже о вероятном рецепте для катастрофы с раскруткой стека, если вы записываете известное поврежденное состояние. - person ; 23.10.2008
comment
Что ты предлагаешь? Изменить дизайн программного обеспечения, чтобы облегчить внедрение диагностического инструмента? - person Steven; 24.10.2008