Можно ли в с# CLR код, запустить в "трассировочном" режиме? Можно ли как-то перехватить вызов Invoke? Как можно написать дебаггер под с# код? Единственное что пока-что приходит в голову - емулировать выполнение CLR-кода, но тут нужно тонна кода на емулятор, а потом... перехватывать код готовых библиотек, ведь есть мостики CLR-Native-CLR, поэтому с емулятором не всё так просто. На с++-cli
это чудо думаю было бы реально реализовать... но... Я думаю есть какой-то особый режим, который легко включается. Видел уже есть "кривые" аналоги VS... Как эта проблема решена в других проэктах?
Как написать дебагер, а точнее трейсер.
Статья предполагает что базовые знания с++ есть. Дебагер можно писать на с++, можно даже на с#, но я предпочитаю с++. Покажу базу - как написать трейсер.
С чего начать. Давайте создадим простенький с#
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 - и будет всё ок, но нет.
Создание 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, если ф-ция не работает, значит не все хандлеры вы описали. Теперь перейдем к...
Создание обработчика. В версии 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....
Апгрейт обработчика. Есть интерфейс 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 )
Полезные ссылки
corerror.h
но... он не удобный. Виртуальный выделенный сервер (VDS) становится отличным выбором
У нас есть Image и TextBlock, через который мы может задавать источник изображения:
Необходимо в контроллере,имея переменную city, простым вызовом
Объясните, пожалуйста, в файле контроллера OrdersController: