c# и debug режим

124
18 июля 2019, 19:00

Можно ли в с# CLR код, запустить в "трассировочном" режиме? Можно ли как-то перехватить вызов Invoke? Как можно написать дебаггер под с# код? Единственное что пока-что приходит в голову - емулировать выполнение CLR-кода, но тут нужно тонна кода на емулятор, а потом... перехватывать код готовых библиотек, ведь есть мостики CLR-Native-CLR, поэтому с емулятором не всё так просто. На с++-cli это чудо думаю было бы реально реализовать... но... Я думаю есть какой-то особый режим, который легко включается. Видел уже есть "кривые" аналоги VS... Как эта проблема решена в других проэктах?

Answer 1

Как написать дебагер, а точнее трейсер.

  1. С чего начать
  2. Создание ICorDebug и ICorProcess
  3. Создание обработчика
  4. Апгрейт обработчика

Статья предполагает что базовые знания с++ есть. Дебагер можно писать на с++, можно даже на с#, но я предпочитаю с++. Покажу базу - как написать трейсер.

  1. С чего начать. Давайте создадим простенький с#

    using System;
    public class Demo {
       public static void Main(string[] args) {
         Console.WriteLine("Hello world");
         }
    }
    

    И соберём его c:\windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /platform:x86 /target:1.exe 1.cs /pdb:1.pdb /debug

У студии есть папка SDK в которой можно найти нужные хедеры. У меня тут SDK\v2.0\include. Понадобятся следующие библиотеки

    #include <windows.h>
    #include "COR\CorHdr.h"
    #include "COR\cor.h"
    #include "COR\cordebug.h"
    ICorDebug* dbg; // Библиотека дебаггера
    ICorProcess* process;//Процесс для отладки. Будет для одного процесса

Поначалу я думал - создам CoCreateIntance, там есть ф-ция CreateProcess - и будет всё ок, но нет.

  1. Создание ICorDebug и ICorProcess. Второй создаётся легко, если создался первый. Есть несколько способов его создать, покажу один из. Если mscoree.dll не получается прилинковать - подключайте ёё через LoadLibrary и GetProcAddress. Есть две ф-ции, одна проверяет версию, другая - создает ICorDebug.

    void main(){
      wchar_t* module = "c:\\yourdebug\\1.exe";
      wchar_t ver[20];
      GetRequestedRuntimeVersion(module,ver,sizeof(ver),&dw);//mscoree.dll
      hr=CreateDebugginInterfaceFromVersion(CorDebugVersion_2_0,ver,&dbg);
      // hr=CreateDebugginInterfaceFromVersion(CorDebugVersion_2_0+1,ver,&dbg); // для .NET 4
      // Если не подходит версия - пробуйте менять первый аргумент
      //TODO: проверка hr
      hr = dbg->Initialize();
      STARTUPINFOW si = {sizeof(STARTUPINFOW),0,};
      PROCESS_INFORMATION pi = {0,};
      // dbg->SetUnmanagedHandler           
      dbg->SetManagedHandler(MgrHandler); // ниже будет
      // TODO: параметры запуска можно будет доделать
      hr =  dbg->CreateProcess(module,module,0,0,true,
        CREATE_NEW_CONSOLE, L"\0\0\0\0", L".", &si, &pi,0,&process);
      // TODO: цикл ожидания конца работы дебагера
      for (int i=0;i<100;i++)
        WaitForSingleObject(pi.hProcess,1000);
      }
    

    Ну... почти готово. Или почти не считается. Долго мучался с SetManagedHandler, если ф-ция не работает, значит не все хандлеры вы описали. Теперь перейдем к...

  2. Создание обработчика. В версии FrameWork 2.0 оказывается нужно поддерживать два каллбека. Ну и... коечего повписывать в обработчик. Нужно везде где можно написать pAppDomain->Continue(); вписать его в каждый обработчик. Весь код приводить не буду, приведу главное. Дальше речь идет только об этом обработчике

    class MGRHandler:public ICorDebugManagedCallback,ICorDebugManagedCallback2{
       // 1 Рассказать какие у нас калбеки
       HRESULT __stdcall QueryInterface(REFIID riid,void**ppvObject){
          if (riid==IID_ICorDebugManagedCallback){
                 *ppvObject=(ICorDebugManagedCallback*)this;//Первый
                  return 0;
                  }
          if (riid==IID_ICorDebugManagedCallback2){
                  *ppvObject=(ICorDebugManagedCallback2*)this;//Второй
                  return 0;
                  }
          return (HRESULT)-1;
          }
       // 2 Вначале нас бросит сюда
       HRESULT __stdcall CreateProcess(ICorDebugProcess* pProcess){
         pProcess->Continue(0);
         }
       // 3 Потом будет создан "домен"
       HRESULT __stdcall CreateAppDomain(ICorDebugProcess *pProcess,
       ICorDebugAppDomain *pAppDomain) {
           pAppDomain->Attach(); // Переводим домен в дебаг режим
           pProcess->Continue(0);
           return 0;};
       // 4 Ставим Continue тут обязательно, желательно везде
       HRESULT __stdcall LoadAssembly(ICorDebugAppDomain *pAppDomain,
        ICorDebugAssembly *pAssembly)  {           
        pAppDomain->Continue(0);return 0;
        }
       HRESULT __stdcall CreateThread(ICorDebugAppDomain *pAppDomain,
        ICorDebugThread *thread) {          
           pAppDomain->Continue(0);return 0;
           };
    

    Аналогично ф-ции Breakpoint,NameChange и другие. Перехват функций нужно делать в модуле, например так:

    HRESULT __stdcall LoadModule(ICorDebugAppDomain *pAppDomain,
    ICorDebugModule *pModule) {
        ICorDebugFunction* fn=0;//ф-ция номер 1 имеет такой код 6000001
        pModule->GetFunctionFromToken(0x6000001,&fn);
        if (fn!=0) fn->CreateBreakpoint(&bp1);
        // TODO: очистить bp1  
        pAppDomain->Continue(0);          
        return 0;
        }
    

    И добавим обработчик BreakPoint

     HRESULT __stdcall Breakpoint(ICorDebugAppDomain *pAppDomain,
     ICorDebugThread *pThread,
     ICorDebugBreakpoint *pBreakpoint) {            
         ICorDebugFrame * f = 0;
         pThread->GetActiveFrame(&f);
         ICorDebugStepper * step = 0;
         f->CreateStepper(&step);
         step->Step(0); // Заставляем работать в шаговом режиме
         // TODO: step освободить когда не нужен будет
         f->Release();
         pAppDomain->Continue(0);  
         return 0;
         }
    

    Да, добавьте MGRHandler MgrHandler; под обработчиком, когда обработчик доконца допишите. Если компилятор ругается пишет слова abstract и error значит не все функции реализованы. Нужно все добавить (современные компиляторы умеют реализовать абстрактные классы сами, если дополнительно сделать два три клика в нужных местах).

Теперь дебагер может кое-как отлаживать программу. Столкнулся с тем... Ой, а где же мой ip....

  1. Апгрейт обработчика. Есть интерфейс ICorDebugILFrame - в нем куча полезностей, локальные переменные, аргументы, и ip. Последний "штрих" программы, правим в обработчике ф-цию:

    HRESULT __stdcall StepComplete(
         ICorDebugAppDomain *pAppDomain,
         ICorDebugThread *pThread,
         ICorDebugStepper *pStepper,
         CorDebugStepReason reason) {
             ICorDebugFrame * f = 0;
             ICorDebugILFrame * ff = 0;
             unsigned ip,tok;
             pThread->GetActiveFrame(&f);
             f->QueryInterface(IID_ICorDebugILFrame , &ff);
             ff->GetIP(&ip);
             f->GetFunctionToken(&tok);
             char buf[30];
             wsprintfA("tok:%x ip:%x\r\n",tok,buf);
             // TODO: вывод на екран
             pStepper->Step(0); // пусть ещё шагает
             pAppDomain->Continue(0);
             return 0;
             }
    

Теперь получился "трейсер", который позволяет по-шагам выполнить ф-цию. Я не упомянул IMetaDataImport - через него можно получить имена всех ф-ций и параметров, остальную нужную информацию найти относительно легко. Я постарался показать 4-ре шага, которые мне было не очень просто найти. Показан чисто "скелет".

P.S. Тесты.

Модуль собран в borland c++ x86.

win2003 x86 - модуль работает (студия 2005)

win7 x64 - модуль работает, но нужно создать 32-битную сборку отлаживаемой программы, т.е. добавить в csc.exe параметр /platform:x86 (иначе ловим ошибку DebuggerError 0x80131C30 )

Полезные ссылки

  • MS debug api http://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/
  • Создание ICorDebug http://lowleveldesign.org/2010/10/11/writing-a-net-debugger-part-1-starting-the-debugging-session/ Часть 2 Часть 3 Часть 4
  • Коды ошибoк HRESULT http://github.com/mrfearless/UASM-with-RadASM/blob/master/UASM64/ErrorCodes.dat Файл с кодами ошибок есть corerror.h но... он не удобный.
READ ALSO
Проверка правильности Image

Проверка правильности Image

У нас есть Image и TextBlock, через который мы может задавать источник изображения:

188
Как внедрить в сервис-контейнер объект с постами из БД?

Как внедрить в сервис-контейнер объект с постами из БД?

Необходимо в контроллере,имея переменную city, простым вызовом

135
Что за аргументы в конструкторе Laravel?

Что за аргументы в конструкторе Laravel?

Объясните, пожалуйста, в файле контроллера OrdersController:

161