Все мы прекрасно знаем, что в операционной системе Windows существуют функции/процедуры, размещаемые внутри динамических библиотек (DLL). Эта особенность является одним из основных отличительных черт современных операционных систем, и Windows тут не исключение.
Но у библиотек имеется одна характерная особенность: они не запускаются через привычные пользователям механизмы, применимые к исполняемым образам .exe (например, запуск двойным щелчком из проводника), вместо этого процедуры/функции/ресурсы библиотеки доступны для использования исключительно из кода приложений. Одним словом просто так вот щелкнуть в проводнике по .DLL-файлу и запустить его не получится. И все же, у специалиста/пользователя время от времени возникает необходимость воспользоваться функциями той или иной библиотеки, в этом случае имеется два основных пути:
- Написать собственное приложение, которое будет использовать требуемые функции [той или иной библиотеки];
- Воспользоваться специализированным средством (утилитой) командной строки rundll32.exe;
Очевидно, что в рамках данной заметки нам интересен именно второй подход. Для его реализации разработчики из MS создали своеобразный "стартер", который вызывает код [заданной] функции (процедуры), размещаемой в одной из доступных в системе библиотек, прямо из командной строки. Идея была реализована Microsoft в виде утилиты под названием rundll32.exe.
RunDll32 - [системная] утилита командной строки, разработанная для загрузки и запуска функций из 32/64-битных библиотек DLL. Тем не менее, ошибочно было бы считать, что утилита rundll32 является средством для "запуска DLL" по аналогии с исполняемым приложением, в действительности же невозможно "запустить DLL", поскольку это не исполняемый образ в классическом понимании (вроде .EXE-образов).
Теоретически rundll32 может использоваться для запуска функций из любых типов файлов, по структуре идентичных с библиотеками DLL: элементов ActiveX (*.ocx), апплетов панели управления (*.cpl), драйверов (*.drv). Бытует мнение, что когда-то функциональные особенности rundll32 были неявными, то есть "скрытыми" от пользователя системы, но при этом самой системой активно эксплуатирующимися. Не совсем понятное утверждение, поскольку у разработчиков никогда и не существовало проблем в том, чтобы набросать алгоритм запуска произвольных функций библиотек и выделить его в отдельный функционал, к тому же в Windows это основа основ. Со временем утилита rundll32 превратилась в достаточно продвинутое средство, и в определенный момент, по причине того, что программа предоставляла возможности общего характера, разработчики решили включить её в базовый комплект ОС в виде отдельной утилиты. В состав Microsoft Windows 95, Windows 98 и Windows Millennium входили программы rundll.exe и rundll32.exe, а в операционных системах Microsoft Windows NT 4.0, Windows 2000 и Windows XP, в сборку была включена единственная утилита с именем rundll32.exe.
Причина подобного отношения кроется в многочисленных проблемах, связанных с использованием rundll32 в своих программных окружениях. Как же без rundll32, спросите Вы? Оказывается, Microsoft активно внедряет специализированные системные утилиты, которые постепенно заменяют функционал полюбившегося многим программного средства. Поэтому, время от времени в процессах Windows 7 можно таки встретить процесс rundll32
, однако встречается подобное явление всё реже и реже.
Алгоритм работы rundll32
В общем то, тут нет никакой революции, поскольку rundll32 представляет собой классическое приложение, выполняющее динамическое связывание функций библиотеки DLL на этапе выполнения. В общих чертах алгоритм работы rundll32 можно описать следующим образом:
- Вызывается функция LoadLibrary, которая загружает выбранную DLL;
- Вызывается функция GetProcAddress, для получения адреса точки входа вызываемой из библиотеки функции;
- Вызывается проверка соответствия функции специфическим требованиям;
- Вызывается сама функция и ей передаются входные параметры;
- Происходит выход из функции по завершению её работы;
- Библиотека DLL выгружается из памяти процесса.
Как увидеть полный путь запуска rundll32
Если в системе ни с того ни с сего вдруг удалось обнаружить запущенный процесс rundll32
, и вам стало интересно, а какой же, собственно, функционал утилита выполняет, то можно определить это по полному пути запуска утилиты rundll32. Посмотреть полный путь запуска, то есть параметры командной строки можно при помощи системной утилиты Диспетчер задач Windows (Task Manager).
Вызовите "Диспетчер задач" (Ctrl+Shift+Esc), перейдите в меню "Вид", выберите пункт "Выбрать столбцы.." и пролистав список вниз, найдите пункт "Командная строка" и отметьте его чекбокс, затем нажмите ОК. Результатом будет появление в главном окне диспетчера задач в параметре "командная строка" полной строки запуска rundll32.
32-битные и 64-битные версии программы
В 64-битных версиях ОС семейства Windows присутствуют 2 варианта программы rundll32.exe:
- 64-битная версия, расположенная в %SystemRoot%\System32\;
- 32-битная версия, расположенная в %SystemRoot%\SysWOW64\.
В 64-битной ОС, для загрузки 64-битной библиотеки DLL может быть использована 64-битная версия rundll32.exe, находящаяся в директории %SystemRoot%\System32\. Напротив, 32-битные программы в 64-битной ОС, обращающиеся к %SystemRoot%\System32\, в целях обеспечения совместимости будут перенаправлены в %SystemRoot%\SysWOW64\ и, соответственно, будут использовать уже 32-битную версию rundll32.exe.
Пример использования rundll32
Для запуска программы rundll32 используется синтаксис командной строки следующего вида:
rundll32.exe <имя_библиотеки_dll>,<имя_функции>
В качестве примера предлагаю разобрать команду запуска апплета "Язык и региональные стандарты" панели управления, вкладка "Форматы":
rundll32.exe shell32.dll,Control_RunDLL intl.cpl,,0
При выполнении данной команды утилита rundll32 вызовет функцию Control_RunDLL, находящуюся в библиотеке shell32.dll, и передаст ей следующие параметры:
Параметр | Описание |
---|---|
hWnd |
Идентификатор (дескриптор) родительского окна, который обычно используется при создании окон в функциях загружаемой библиотеки DLL. |
hInstance |
Дескриптор (заголовок экземпляра) выбранной библиотеки DLL. Иначе, стартовый адрес процесса DLL в адресном пространстве. В нашем случае - библиотеки shell32.dll. |
lpCmdLine |
Командная строка, передаваемая библиотеке. То есть параметры, передаваемые самой библиотеке. В нашем случае intl.cpl,,0; |
nCmdShow |
Режим отображения окон выбранной библиотеки. (данные, передаваемые функции CreateProcess). |
Теперь давайте отметим и некоторые требования к синтаксису rundll32:
- Параметр, описывающий точку входа вызываемой функции (метка EntryPoint) чувствителен к регистру. Это означает, что значение Control_RunDLL не тоже самое что control_rundll. Довольно часто неправильное указание регистра символов вызываемой функции приводит к ошибкам отказа в обнаружении последних.
- Функция (в приведенном выше примере — функция Control_RunDLL) должна самостоятельно выполнять анализ командной строки и идентифицировать отдельные ее аргументы.
- Утилиты rundll/rundll32 ищут указанную библиотеку DLL в стандартных местоположениях.
В 64-разрядных версиях Windows (начиная с Vista) системные библиотеки размещаются в трёх каталогах: %WinDir%\System32, %WinDir%\SysWOW64 и %WinDir%\WinSxS. Причем последний используется компонентной моделью (Side-by-Side).
В нашем примере библиотека shell32.dll располагается по известному в переменной
%PATH%
пути, поэтому мы и указываем её в сокращенном виде. Чтобы быть уверенным, что будет загружена именно интересующая нас библиотека DLL, рекомендуется конкретизировать полный путь к файлу библиотеки. В противном случае может возникнуть ситуация, когда в системе может присутствовать одноименная библиотека, находящаяся в стандартном местоположении (например, по путям, определяемым%PATH%
), а та, которую хотите использовать Вы, находится по неизвестному системе пути, в этом случае утилитой rundll32 будет вызвана первая библиотека. - <имя_библиотеки_dll> не должно содержать недопустимых символов: пробелов, запятых и кавычек (ограничение это накладывается кодом анализатора командной строки утилиты rundll32).
Ранее, во времена Windows 95/98, к синтаксису командной строки утилиты rundll32 применялись достаточно жесткие требования. Например, чрезвычайно важным являлось наличие запятой (",") между параметром <имя_библиотеки_dll> и именем вызываемой функции. Если запятая опускалась, утилита rundll32 "по-тихому" завершала работу, не выполняя никаких действий с библиотекой. Кроме того, между параметром <имя_библиотеки_dll>, символом запятой и названием вызываемой функции не должно было быть никаких пробелов. Но, со временем анализатор командной строки утилиты претерпевал изменения и эволюционировал, и в данный момент, синтаксис rundll32 позволяет применять пробел между именем библиотеки и именем функции.
Правила создания функций
Очевидно, что в создаваемую нами собственную библиотеку DLL необходимо поместить функцию со следующими входными параметрами (пример описания на Ассемблере):
1 2 3 4 |
proc EntryPoint hWnd,hInstance,lpCmdLine,nCmdShow . . . ret endp |
При создании экспортируемой функции (в примерах выше она имеет псевдо-имя EntryPoint) необходимо учитывать следующие моменты:
- Вместо имени EntryPoint, желательно указать фактическое имя функции. Обратите внимание, что "точка входа", используемая программой rundll32, не зависит от функции (точки входа) DllEntryPoint, которая в 32-разрядных библиотеках DLL осуществляет обработку процессов и оповещение о подключении и отключении потоков. Это точка входа в саму функцию.
- Функцию, являющуюся точкой входа для программы rundll32, необходимо определить, используя соглашение о вызовах _stdcall (в C++ по умолчанию для атрибута _stdcall используется значение CALLBACK). Иначе, по умолчанию будет использоваться другое соглашение о вызовах _cdecl. Это приведет к аварийному завершению работы программы rundll32 после вызова данной функции.
Функции, являющейся точкой входа, передаются следующие параметры:
Параметр | Описание |
---|---|
hWnd | Идентификатор (дескриптор) родительского окна, который обычно используется при создании окон в функциях загружаемой библиотеки DLL. |
hInstance | Дескриптор (заголовок экземпляра) выбранной библиотеки DLL. Иначе, стартовый адрес процесса DLL в адресном пространстве. |
lpCmdLine | Командная строка, передаваемая библиотеке. Данная строка представляет собой последовательность символов, завершающуюся символом с кодом 0. |
nCmdShow | Режим отображения окон выбранной библиотеки. (данные, передаваемые функции CreateProcess). |
Пример создания DLL с экспортируемой функцией
Я написал код примера на языке Ассемблера, уж не обессудьте, поскольку пока лишь на этом языке я хоть как-то могу внятно выражать свои мысли :) Компилируется исходный код посредством компилятора FASM, версией для Windows (FASMW.EXE). В библиотеке я описал одну единственную экспортируемую функцию под названием ShowErrorMessage
, которая выводит на экран окно ошибки с заданными параметрами. Создадим файл testdll.asm и добавим в него следующее содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
format PE GUI 4.0 DLL ; Формат PE DLL. Версия GUI 4.0. entry DllEntryPoint ; Точка входа include '%include%\win32a.inc' ; Стандартный инклюд ;--- сегмент данных -------------------------------------------------------- section '.data' data readable writeable _text db 'This is test message',0 ; Текст ошибки _title db 'This is title',0 ; Текст в заголовке окна. ;--- сегмент кода ---------------------------------------------------------- section '.text' code readable executable ;--- DllEntruPoint: точка входа, должна быть обязательно-------------------- proc DllEntryPoint hinstDLL,fdwReason,lpvReserved mov eax,TRUE ret endp ;--- ShowErrorMessage(HWND hWnd,DWORD dwError);-наша функция по выводу окна- proc ShowErrorMessage hWnd,hInstance,CmdLine invoke MessageBox,[hWnd],[CmdLine],_title,MB_ICONERROR+MB_OK ret endp ;--- подключение функций из системных библиотек----------------------------- section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',user32,'USER32.DLL' include 'api\kernel32.inc' include 'api\user32.inc' ;--- секция экспорта ------------------------------------------------------- ; как раз тут то мы и указываем, какие функции будут экспортируемыми. section '.edata' export data readable export 'testdll.dll',ShowErrorMessage,'ShowErrorMessage' ;--- секция переносимых процедур ------------------------------------------- section '.reloc' fixups data readable discardable |
После компиляции у нас в рабочей директории создается файл testdll.dll, поэтому для проверки функционала можно вызвать команду:
rundll32 testdll.dll,ShowErrorMessage ThisIsTestString
А какие есть еще варианты загрузки dll?
Вы имеете в виду пользовательский метод или общесистемный? то есть приложения (утилиты ком. строки) или системные механизмы загрузки (вроде Loadlibrary)?
Абсолютно голословное утверждение про run32dll. exe о том что он якобы служит для загрузки в память и вызова функций библиотек runtime ( так принято правильно называть файлы *. dll), для этого в операционной системе есть API. Нет особой разницы между загрузкой в память, передачей в стеке параметров ( причины вызова) для вызова процедур находящихся внутри библиотек runtime, освобождения памяти при закрытии процесса или приложения использующего библиотеку 64 либо 32 bit. A вот способы компиляции (если быть точным, связывание имён функций экспорта и импорта процедур ) приложения разделяются на : раннюю при трансляции (статическкую) и позднею во время выполнения модуля ( динамическую) . Не поленитесь автор прочтите одну из книг Владислава Пирогова.
PS Если проводить параллели то можно увидит, что очень похожие механизмы работы были за долго до появления ос Windows NT в ос MSDOS в файлах оверлеях.
Ага, кажется я вас понял. Вы предлагаете указать, что при запуске rundll32 создается пространство процесса этого приложения и происходит динамическое связывание с функциями вызываемой библиотеки (указанной в аргументах)?