24 November, 2009

Поддержка .NET Framework 4.0 в dotTrace 4.0 (Шаг №2)

Неожиданно быстро (всего за один рабочий день) получилось сделать нормальную поддержку .NET Framework 4.0 (реализован ICorProfilerCallback3 и используется ICorProfilerInfo3 + Enter3/Leave3/Tailcall3) в dotTrace 4.0, правда пока не совсем полноценную:

  • нет поддержки нескольких .NET Framework'ов
  • не реализован режим attach to process
Все остальное воркает в лучшем виде.

22 November, 2009

Поддержка .NET Framework 4.0 в dotTrace 4.0

По запросам дружественного проекта ReSharper был наконец вылизан режим совместимости с .NET Framework 4.0. Это конечно не есть полная поддержка нового фреймворка, но для начала это тоже неплохой шаг.

Аккуратность прежде всего

Есть такой режим совместимости в .NET Framework 4.0 - в этом режиме можно запустить профайлер для .NET Framework 2.0. Устанавливается он таким образом - переменной окружения COMPLUS_ProfAPI_ProfilerCompatibilitySetting присваиваем значение EnableV2Profiler. Так вот, если ненароком добавить пробел в конце присваиваемого значения, то режим совместимости просто не включается. А заметить это пробел очень трудно, если конечно не знать где искать.

07 November, 2009

GC и выгрузка модулей

Обнаружил очередную приколку в .NET Profiling API - есть событие ObjectsAllocatedByClass, где в качестве одно из аргументов передается массив ClassID. Так вот, если вызвать GetClassIDInfo/GetClassIDInfo2 для этого массива, то можно получить ModuleID для которого тоже можно вызвать GetModuleInfo. Все будет хорошо до тех пор пока GC не произойдет во время выгрузки application domain. Вот тут-то и появится проблема - GetModuleInfo может вернуть CORPROF_E_DATAINCOMPLETE. Так в чем же дело?

В MSDN сказано, что при выгрузке домена все классы и функции этого домена становятся недействительными - мы уже знаем что это ложь: во время выгрузки происходят компиляции функций и выделения объектов из самого выгружаемого домена до получения первого ModuleUnloadStarted. Сбор же мусора начинается как раз после получения всех ModuleUnloadStarted. Соответственно объекты из уже выгруженных модулей попадают в статистику GC. Занавес!

03 November, 2009

Ода гордости

Как это однако приятно увидеть, что результатом твоего труда пользуются в компании Microsoft. Смотреть тут! Для тех кто не в теме: стектрейсы сделаны моим детищем - JetBrains dotTrace 3.1.

23 October, 2009

Сюрприз

Ну вот, облизанный со всех сторон код рушится на моих глазах: RuntimeThreadSuspended и RuntimeThreadResumed оказывается могут вызываться из произвольного треда, а не только из того, что указан в аргументе. Получается, что повсеместно используемый TlsGetValue() именно в этих функциях со свистом пролетает мимо кассы. До сего момента я считал, что этим страдала только ThreadDestroyed, но там я выкрутился...

P.S. Иногда полезно быть параноиком и маниакально проверять в коде казалось бы очевидные вещи.

P.P.S. Если вы пользуетесь остановкой треда посредством DoStackSnapshot, то будьте готовы также получать и корректно обрабатывать RuntimeThreadSuspended и RuntimeThreadResumed.

21 October, 2009

Полезный тул

Каждый раз с завидным постоянством забываю название утилиты, которая показывает установленные .NET Compact Framework'и. Имя ей - cgacutil.exe. Надеюсь, что теперь не забуду.

23 September, 2009

Структура на стеке инструментируемой функции

Занимаясь инструментацией я пришел к выводу, что очень полезным является создание некой области на стеке функции для внутренних нужд профайлера. Для этого категорически необходимо создать структуру нужного размера. Увы, stackalloc не подходит так как требует довольно много накладных расходов и особенно под x64. Попытка использовать свой модуль с уже скомпилированными структурами во время работы профайлера приводит к зависанию во время загрузки оного. Поэтому пришлось в каждый модуль профилируемой программы эмитировать необходимую структуру.

Теперь о важном. Первое, что необходимо сделать - это найти mdAssemblyRef для mscorlob.dll и найти/создать mdTypeRef для System.ValueType - это необходимо, так как любая структура обязана иметь в качестве базового класса System.ValueType. Важно также чтобы наша структура имела атрибуты tdNotPublic, tdSequentialLayout, tdClass, tdSealed, tdBeforeFieldInit. И не забудьте установить необходимой вам class layout. Результатом всех этих действий будет mdTypeDef, который нужно засунуть в LocalVarSig.

Кстати в эту же структуру можно записывать (опять же с помощью той же инструментации) информацию о самой функции для профайлера - ну там statement count или еще что-нибудь нужное.

21 August, 2009

IL calling conversion

Единственная инструкция которая позволяет вызывать native код из IL есть calli. Возникает вопрос как нужно написать прототип функции на C++ для того чтобы передать аргументы из managed кода.

Для платформы x86 требуется чтобы функция была fastcall - первые два аргумента (те что передаются через регистры ECX и EDX) совпадают, а вот те аргументы которые лежат на стеке болжны быть описаны в обратном порядке (смотри MSDN x86 calling conversion а также Microsoft specific ECMA-335 Partition II 15.5.6.1). Привожу пример:

void fastcall MyOwnCallback(int arg0, int arg1, int arg4, int arg3, int arg2);

С платформой x64 еще проще используется стандартный calling conversion (смотри MSDN x64 calling conversion).

18 August, 2009

Очередной серьезный пробел в MSDN

Как жаль, что MSDN теперь пишут так халтурно, лучше бы сделали нормальную документацию, а не переводили это дерьмо на кучу языков отличных от английского. И вот я снова вляпался: есть такая функция SetILInstrumentedCodeMap() в качестве 4-ого аргумента у которой должен быть массив COR_IL_MAP. Нигде в MSDN не сказано, что этот самый массив обязательно нужно выделать с помощью CoTaskMemAlloc(). Уродство, так как это есть существенное поведение функции, увы, не отраженное в документации.

P.S. Мне помогло разобраться только капание в Shared CLI.

30 July, 2009

Пара слов про наследование handles

Все вы наверное не раз пользовались классом System.Diagnostics.Process для запуска процессов из под .NET приложения. Но не все знают что если ProcessStartInfo.UseShellExecute установлен в false, то все handle исходного процесса наследуются дочерним, если конечно иное не указано для конкретного handle. Все вышесказанное касается в одинаковой мере и .NET Framework, и .NET Compact Framework.

P.S. Кстати, всем C++ разработчикам известная, функция fopen по умолчанию не уcтанавливает флаг _O_NOINHERIT. Начиная с Visual Studio 2005 добавлен новый параметр N, который собственно и должен выставлять этот флаг в fopen. Увы в Windows CE нет такой возможности. Более того так как lpSecurityAttributes в функции CreateFile должен быть всегда NULL, то и установить переменную bInheritHandle в SECURITY_ATTRIBUTES тоже невозможно.

14 July, 2009

Найди отличия

Есть два по сути эквивалентных фрагмента кода на C#:
a(x => b().c(x));
и
a(b().c);
В какой момент произойдет NullReferenceException для каждого из вариантов, если b() всегда возвращает null?

29 April, 2009

Swap при помощи XOR

Обмен двух переменных без использования дополнительной переменной с помощью XOR. http://en.wikipedia.org/wiki/XOR_swap_algorithm. Забавно то, что как-то до сих пор об этом не думалось.

11 April, 2009

Wildcard comparer

Все наверное писали что-то подобное на тестах при поступлении на работу. И вот свершилось, wildcard comparer реально потребовался. Особенности алгоритма: несколько следующих друг за другом * считаются одной.
bool WildcardCompare(LPCWSTR pFilter, LPCWSTR pString)
{
    while (*pString && *pFilter != L'*')
    {
        if (*pFilter != *pString && *pFilter != L'?')
            return false;
 
        ++pFilter;
        ++pString;
    }
 
    LPCWSTR pBaseFilter;
    LPCWSTR pBaseString;
 
    while (*pString)
    {
        if (*pFilter == L'*')
        {
            if (!*++pFilter)
                return true;
 
            pBaseFilter = pFilter;
            pBaseString = pString;
        }
        else if (*pFilter == *pString || *pFilter == L'?')
        {
            ++pFilter;
            ++pString;
        }
        else
        {
            pFilter = pBaseFilter;
            pString = ++pBaseString;
        }
    }
 
    while (*pFilter == L'*')
        ++pFilter;
 
    return !*pFilter;
}

03 April, 2009

Разные команды, разный подход к одной задаче

Очередная несовместимость .NET Profiling API для .NET Framework и .NET Compact Framework. Важный этап каждого приложения - это его завершение. И тут мы снова видим совершенно разное поведение. При завершении .NET Framework я не вижу ThreadDestroyed для главного thread и не наблюдаю вызовов AppDomainShutdownStarted/AppDomainShutdownFinished для appllication domain приложения. C этим в принципе можно смириться, так как есть вызов Shutdown означающий завершение приложения. С .NET Compact Framework все сложнее. Я наблюдаю завершение главного thread, после чего создается специальный thread для выгрузки application domain приложения и АСИНХРОННО по отношению как минимум к вызову AppDomainShutdownFinished вызывается Shutdown в главном thread. Что на мой взгляд есть очень серьезный bug.

25 March, 2009

Очередные косяки в .NET Profiling API

Есть такой event JITInlining который спрашивает у профайлера будем ли в функцию A инлайнить функцию B. Обычно эти event'ы возникают между JITCompilationStarted и JITCompilationFinished. Что на самом деле довольно логично: начали компиляцию, проинлайнили чего-нибудь, закончили компиляцию. Но вот, откуда ни возьмись, появляются независимые JITInlining без обрамления начала и конца компиляции. Поиследовав немного этот вопрос я нашел, что token функции в которую производятся инлайны всегда 0x06000000 - что на самом деле говорит нам, что токена у такой функции нет. Token класса такой же пустой - 0x02000000. Выводы? А их нет - это косяк, хотя и довольно безобидный.

Еще косячок посерьезнее - после event'a AppDomainShutdownStarted могут происходить компиляции finalizers в application domain для которого был вызван AppDomainShutdownStarted. С другой стороны в документации написано, что id application domain стабилен и правилен только до окончания вызова AppDomainShutdownStarted.

Освобождаем буфер в std::string

Задача вроде банальная, но если приглядется то ни один метод напрямую не позволяет освободить буфер у std::string. Вот мое решение этой задачки:
str.swap(std::wstring());
Естественно все работает и для std::wstring.

18 March, 2009

Банальная разница

Ну все наверно знают функцию GetCommandLine(). Ну скажите какого черта нужно было сделать эту функцию по разному в Windows и в WindowsCE? Ну почему в последнем случае executable file не добавляется в начало в качестве первого аргумента? Другими словами, если мы просто запустили программу без аргументов под WindowsCE, то всегда будем получать пустую строчку в результате вызова GetCommandLine()!

17 March, 2009

Puzzle: Fast Bit Counting

Тут несколько способов быстро узнать количество бит в слове.

07 March, 2009

Locks and exceptions do not mix

Статья рассказывает о том, что есть серьезная проблема с локами в C# при использовании thread abort.

05 March, 2009

Нахождение наименьшего общего делителя (НОД)

Нашел несколько вариантов алгоритма нахождения НОД, реализовал их на С++ и решил сравнить по скорости. Вопреки моему ожиданию, а я поставил на бинарный алгоритм Евклида, победил обычный алгоритм Евклида (разница примерно в 1.3 раза). Его тут и привожу:
size_t NOD(size_t a, size_t b)
{
    while (a != 0 && b != 0)
        if (a >= b)
            a = a % b;
        else
            b = b % a;
    return a + b;
}

21 February, 2009

Development Server не любит Vista

Под моей Vista x64 не работает Development Server довольно странным образом: он запускался, но ни один browser не мог к нему законнектится. Оказалось, что если заменить в строке browser'а localhost на 127.0.0.1, то все тут же заработает. Причину такого поведения вижу в том, что Vista поддерживает IPv6 и по умолчанию localhost транслируется в ::1. А на соответствующем порту IPv6 наш browser никто не слушает.

20 February, 2009

Не работает асинхронная закачка в WebClient

Обнаружил маленькую проблему с классом System.Net.WebClient. При попытке использования асинхронной закачки с HTTP сервера посредством DownloadStringAsync или DownloadDataAsync не приходили эвенты DownloadStringCompleted и DownloadDataCompleted соответственно. Проблема решиласть следующим оьразом:
ThreadPool.QueueUserWorkItem(state => client.DownloadStringAsync(uri), null);

18 February, 2009

Debug Visual C++ Redistributable Package своими руками

Довольно частое явление когда у клиента девственно чистый компьютер - в смысле без Visual Studio, а вам необходимо запустить debug версию вашего приложения на C++ для отладки например. Если ваша сборка использует динамическую линковку системных DLL, то почти наверняка ваше приложение не загрузится. Скорее всего причина кроется в том, что у клиента не установлен Visual C++ Redistributable Package. На сайте Microsoft лежат такие package, но только для release, для debug вы их там не найдете:
Так как же быть? Создадим такой package сами. Открываем Visual Studio, создаем новый solution. В диалоге выбора типа проекта выбираем Other Project Typoes|Setup Project. Потом добавляем необходимые merge module в проект и компилируем. Учтите, что для каждой платформы будь то x86 или x64 необходимо создать свой project в solution. Не забываем добавлять соответствующие policy. В х64 вариант обьязательно добавляем merge module для x86. Ниже приведен список необходимых merge module для:
Visual Studion 2005 (DebugCRT x86)
  • Microsoft_VC80_DebugCRT_x86.msm
  • policy_8_0_Microsoft_VC80_DebugCRT_x86.msm
Visual Studion 2005 (DebugCRT x64)
  • Microsoft_VC80_DebugCRT_x86.msm
  • Microsoft_VC80_DebugCRT_x86_x64.msm
  • policy_8_0_Microsoft_VC80_DebugCRT_x86.msm
  • policy_8_0_Microsoft_VC80_DebugCRT_x86_x64.msm
Visual Studion 2008 (DebugCRT x86)
  • Microsoft_VC90_DebugCRT_x86.msm
  • policy_9_0_Microsoft_VC90_DebugCRT_x86.msm
Visual Studion 2008 (DebugCRT x64)
  • Microsoft_VC90_DebugCRT_x86.msm
  • Microsoft_VC90_DebugCRT_x86_x64.msm
  • policy_9_0_Microsoft_VC90_DebugCRT_x86.msm
  • policy_9_0_Microsoft_VC90_DebugCRT_x86_x64.msm
Если вы используете MFC, то дополнительно добовляете MFC merge module в соответствующий проект по аналогии - имена должны содержать DebugMFC.
Остается только скомпилировать ваш solution и в результате вы получите msi для установки на компьютере клиента. Рекомендую также правильно и солидно назвать исталляцию и сменить имя msi файла, чтобы на стороне клиента это все не выглядело поделкой на коленке.
P.S. Стиль когда нужные сисетмные DLL кладутся в проект рядом с вашим приложением на мой взгляд является анахронизмом, хоть и работает. С другой стороны мне несложно придумать конфигурацию, когда такой фокус уже не прокатит. Самый простой пример: ваше приложение для x86 и для x64 лежат в одном каталоге.
P.P.S Все доступные merge module хрянятся в "%CommonProgramFiles%\Merge Modules".

14 February, 2009

Чего нету в Profiling API для .NET Compact Framework 3.5

На данный момент не реализованы в ICorProfilerInfo2 for .NET Compact Framework v3.5.7283 следующие методы:
  • GetClassFromObject
  • GetEventMask
  • GetFunctionFromIP
  • GetFunctionFromToken
  • SetFunctionIDMapper
  • GetTokenAndMetadataFromFunction
  • GetModuleMetaData
  • GetILFunctionBody
  • GetILFunctionBodyAllocator
  • SetILFunctionBody
  • GetAppDomainInfo
  • SetFunctionReJIT
  • SetILInstrumentedCodeMap
  • GetInprocInspectionInterface
  • GetInprocInspectionIThisThread
  • GetThreadContext
  • BeginInprocDebugging
  • EndInprocDebugging
  • GetStringLayout
  • GetCodeInfo2
  • GetClassFromTokenAndTypeArgs
  • GetFunctionFromTokenAndTypeArgs
  • EnumModuleFrozenObjects
  • GetThreadAppDomain
  • GetObjectGeneration
  • GetNotifiedExceptionClauseInfo
Дополнительно могу сказать, что GetModuleInfo() всегда в качестве базового адреса модуля возращает NULL.

Устанавливаем output path для C++ проектов из MSBuild

Задача не так проста как кажется с первого взгляда - я потратил несколько часов чтобы ее решить. Итак, нам нужно сгенерировать из MSBuild'а файлик с расширением vsprops и примерно таким содержимым:

<?xml version="1.0"?>
<visualstudiopropertysheet
  ProjectType="Visual C++"
  Version="8.00"
  Name="my project name"
  OutputDirectory="my output path">
</VisualStudioPropertySheet>

Далее мы должны передать имя этого файлика в таску VCBuild через параметр Override. При вызове дочернего MSBuild нужно использовать Properties прмерно такого выда:

<msbuild
  Projects="My.sln"
  Targets="Rebuild"
  Properties="$(BuildSolutionProperties);Configuration=Release;Platform=Win32;VCBuildOverride=My.vsprops;" />

Вот собственно и все...

06 February, 2009

Компилируем сборки для .NET 1.1 под Visual Studio 2008

Для начала нужно поставить .NET Framework 1.1. Теперь мы должны создать target файл, который мы затем подключим в csproj. Проблема в том, что набор опций csc.exe для .NET 1.1 немного не совпадает с набором опций для .NET 3.5. Поэтому некоторые опции задавлены. Вот пример такого файла с именем Csc11.Targets:

<?xml version="1.0" encoding="utf-8"?>
<!-- Included into the project files right after MS common targets. -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
    <PropertyGroup>
        <UseHostCompilerIfAvailable>False</UseHostCompilerIfAvailable>
        <TargetFrameworkVersion>v1.1</TargetFrameworkVersion>
        <ErrorReport></ErrorReport>
        <NoWarn>1591</NoWarn>
        <AssemblyOriginatorKeyFile></AssemblyOriginatorKeyFile>
        <CscToolPath>$(WinDir)\Microsoft.NET\Framework\v1.1.4322</CscToolPath>
    </PropertyGroup>
</Project>

Далее модифицируем csproj. Внимание, наш импорт должен быть последним в списке импортов!

<Import Project="Csc11.Targets" />

На этом все должно заработать. После модификации файла csproj, при загрузке, вам могут задать вопрос о том что ваш проект имеет внешние ссылки. Ответить нужно это вопрос нужно так:

04 February, 2009

А знаете ли вы что...

...размер файла в NTFS c атрибутом Compressed не может превышать 32Gbytes.

Получение полного имени OS

Зачем я публикую эту информацию? Все просто, когда она мне понадобилась я потратил кучу времени, чтобы ее найти в достаточно полном объеме.

Сразу оговорюсь, что OS семейства Windows 95/98/Milenium/NT 3.5x/NT 4.0 я не рассматриваю - так как у меня есть причины считать, что эти OS уже не встречаются сегодня достаточно широко.

Теперь несколько комментариев к аргументам функции. processorArchitecture получается при помощи native вызова GetNativeSystemInfo(). GetVersionEx() дает нам majorVersion, minorVersion, buildNumber, csdVersion, suiteMask и productType. GetProductInfo() - vistaProductType. А флаги IsXPTabletPC, IsXPMediaCenter, IsXPStarter, Is2K3ServerR2 - при помощи нескольких вызовов GetSystemMetrics() с соответствующими аргументами.

Как найти Adaption Kit Updates (AKU)?

Это достаточно важное число и оно по сути эквивалентно номеру ServicePack в обычных Windows. Найти его можно в реестре HKEY_LOCAL_MACHINE\SYSTEM\Versions|Aku

Секреты IL инструментации и профайлерного API для .NET Framework

По роду своей деятельности я занимаюсь в том числе и инструментацией IL. Задача состоит в инжекции в оригинальный IL callback'ов: в начало каждого стейтмента (вынимается из PDB) и в каждую точку выхода из функции. С точками возврата есть две проблемы: префикс tail. и throw/rethrow.

Первый в реальных программах практически не встречается, но при встрече гарантирует автору инструментатора головную боль. По спецификации после префикса tail. должен следовать call/calli/callvirt, а после него обязательно ret. Все бы хорошо, но по спецификации на ret разрешен переход (и какой урод это придумал). Объясню более подробно - вы должны проинструментировать все точки выхода из программы, с одной стороны вы не имеете права вставлять свои команды между tail., call/calli/callvirt и ret, а с другой стороны вы должны вставить перед командой ret код, в случае перехода на этот самый ret. Выход один - править структуру метода...

Второй вариант коварнее. При кидании exception'а невозможно на основании только кода метода понять куда нас этот exception занесет. Мы можем остаться в том же самом методе - в обработчике exception, а можем вылететь из метода как пробка из бутылки на несколько функций наверх по стеку. К чему я все это говорю? А к тому - слава великому Аллаху, что как минимум одному сотруднику компании Microsoft пришла в голову мысль добавить немного жизненно нужных event'ов в профайлерное API. Особый интерес представляют: ExceptionUnwindFunctionEnter, ExceptionUnwindFunctionLeave и ExceptionCatcherEnter.

И еще, в свете вышесказанного, флаги COR_PRF_MONITOR_EXCEPTIONS, COR_PRF_MONITOR_ENTERLEAVE и COR_PRF_MONITOR_CODE_TRANSITIONS должны всегда устанавливаться вместе. Последний флаг - это вообще магия без него нет метаданных в памяти для mscorlib и нет 90% Enter/Leave/Tailcall или Enter2/Leave2/Tailcall2 для managed кода.

А напоследок граф переходов обработчика exception так сказать для полноты картины:


22 January, 2009

Непреодолимые приступы нежности к Microsoft

И снова .NET Compact Framewok! Читаем документацию для методов System.Type.InvokeMember. Из прочитанного видим, что эти методы поддерживаются .NET Compact Framewok (MSDN VS2008) - есть значек рядом с методом.

Используем в приложении, запускаем и ... получаем NotSupportedException. Берем .NET Reflector и с удивлением выясняем, что метод вообще не реализован! Другими словами работающей рефлексии в .NET Compact Framewok v3.5.7283 вообще нет!

P.S. После начинаем вчитываться в документацию и случайно видим такую строчку в одной из двух доступных реализаций сего метода: NotSupportedException - The .NET Compact Framework does not currently support this method. Лезем в MSDN в и-нете - а вот там уже все нормально.