آموزش درایور نویسی - قسمت اول - سلام دنیا
از این به بعد بخشی از این وبلاگ به آموزش درایور نویسی در ویندوز اختصاص خواهد یافت. مهمترین دلیل این کارم نبودن منابع فارسی در این زمینه است. و دلیل دیگر این که خودم دوست دارم بهتر این موضوع را یاد بگیرم. سعی می کنم در ابتدا یکسری آموزش های ساده بگذارم بعد وارد مباحث پیچیده تر بشوم.
خوب بریم سر اصل مطلب ...
مقدمه
مطمعنا اکثر افرادی که کارشون مرتبط به کامپیوتر هست یا با کامپیوتر دائما سرو کله می زنند و چیزهای جدید رو امتحان می کنند حتما یک آشنایی مختصری از درایورها دارند. خصوصا موقع نصب سیستم عامل که در انتها باید درایور دستگاهای مختلف مثل مانیتور، کارت گرافیک، کارت شبکه، پرینتر و غیره را نصب کنید و می دانستیم که این کار برای این است که سیستم عامل دستگاه مذکور را بشناسد. البته درایورها فقط برای این هدف ساخته نمی شوند و کاربردهای مختلفی دارند که به غیر از درایور راه انداز دستگاه های متصل به سیستم هر جا نیاز به تغییر در سطح کرنل سیستم عامل باشد یا درایوری که در برخی نرم افزارها مانند آنتی ویروس، فایروال، IPS، VPN Server و ... بکار می رود. برای دیدن بهتر کاربردهای درایورها بهتر است نکاهی به پوشه زیر روی سیستم خود بیاندازید.
c:\windows\system32\drivers
در این مسیر می بینید که پر از فایل های با پسوند sys است. روی هر کدام خواستین کلیک راست کنید بعد properties بگیرید سربرگ Version قسمت Description یک توضیح مختصری داده که این درایور برای چیست. من برای اینکه راحتتر این کار انجام داده بشود یک برنامه ساده نوشتم که توضیحات همه درایورها را یکجا نشان بدهد، تصویر زیر بخشی از این توضیحات گرفته شده از آن فایل هاست. لازم به ذکره همه درایورها لزوما در این مسیر قرار نمی گیرند، از این لینک می توانید این ابزار را دانلود کنید.
اینجا کلی درایور است که ماکروسافت برای دستگاه ها و چیپ های مختلف آماده کرده است. چون من این اطلاعات را داخل VM گرفتم یکسری درایورهای Vmware هم هست. اگر روی سیستمون فایروال یا آنتی ویروس داشته باشین درایور این محصولات نیز داخل این لیست قرار می گیرند.
پیش نیازهای این آموزش
لیستی از دانشها و ابزارهایی که برای توسعه یک درایور برای این آموزش نیاز دارید.
- آشنایی خوب با زبان برنامه نویسی C
- دانلود بسته 7.1 WDK از این آدرس، این نسخه برای سیستم عامل های Windows 7, Windows Vista, Windows XP, Windows Server 2008 R2, Windows Server 2008, and Windows Server 2003. کاربرد دارد این بسته شامل تمام ابزارها، کتابخانه ها و هدرهایی که برای درایور نویسی نیاز دارید را همراه خود دارد. حجم این بسته حدود ۶۰۰-۷۰۰ مگابایت است. اگر برای ویندوز ۸ می خواهید به این صفحه مراجعه کنید. بسته ای که برای ویندوز ۸.۱ است فاقد ابزارهای کامپایل است در نتیجه ابتدا باید Visual Studio نصب کرده باشید بعد WDK 8.1 را نصب کنید. کلا از بعضی کارهای ماکروسافت در عجب می مانم.
- یک ماشین مجازی برای اجرا کردن و تست درایور نوشته شده (اختیاری بخش نکات مهم را ببینید) می توانید از VirtualBox یا Vmware استفاده کنید. VirtualBox اپس سورس است ولی متاسفانه سایت بر روی ایران بسته است ولی میتوانید از سایت www.filehippo.com ابزار را دانلود کنید. VMware تجاری است که آن هم با کمی جستجو پیدا می شود.
- ابزار OSRLoader برای لود کردن درایور، از این لینک دانلود کنید
- ابزار DbgView که از این لینک قابل دریافت است
- یک ادیتور مانند ++Notepad یا Vim یا هر چیز دیگری که با آن راحت هستین، البته از Visual Studio هم می توانید استفاده کنید اگر بخواهید از محیط Visual Studio کامپایل کنید نیاز است تنظیماتی را انجام دهید.
نکات مهم
می خواستم نکات مهم رو در انتها بگذارم ولی بهتر دیدم همین اول باشد قبل از اینکه ادامه بدهیم.
دلیل اینکه گفتم از یک ماشین مجازی استفاده کنید این است که کلا درایور به خاطر اینکه در سطح کرنل سیستم عامل اجرا می شود دارای بالاترین حق دسترسی نیز است و در این سطح می توان هر کاری کرد (استفاده از این قبیل دسترسی ها برای یک فرد بداندیش می تواند منجر به تولید انواع بدافزارهای مختلف با اهداف پلید شود )، در واقع کوچکترین خظایی منجر به بهم ریختن روند کار سیستم عامل و به نمایش در آمدن صفحه آبی مرگ (Blue Screen of Death) که کم بیش همه باهاش روبرو شدن و آشنا هستن می شود. در نتیجه برای جلوگیری از این اتفاق و صدمه ندیدن سیستم عاملی که خود شما داخلش هستین و توسعه راحت تر و تست درایور باید یک ماشین مجازی داشته باشید خصوصا که بعد برای دیباگ درایور از ماشین مجازی استفاده میکنیم. دلیل دیگر استفاده از ماشین مجازی این است که شما بعد نیاز دارید درایور خود را با سیستم عامل های مختلف سازگار کنید. در نتیجه باید سیستم عامل های مختلف یکجا کنار دستتان باشد.
شروع کد نویسی
خوب میرسیم به کد نویسی، عادت و رسمی است که اکثر برنامه نویس ها در شروع یادگیری یک زبان یا تکنولوژی جدید ابتدا اقدام به نوشتن برنامه "سلام دنیا" میکنند. ما هم از این رسم تبعیت میکنیم. یک برنامه ساده که شما را با مفاهیم اولیه درایور نویسی آشنا سازد. حالا برای شروع این فایل ها را بسازید همه را در یک پوشه مثلا به نام hello قرار دهید.
فایل makefile با این محتویات. توجعه کنید که این فایل نباید هیچ پسوندی داشته باشد. بعضی ادیتورها اگر فایل بدون پسوند وارد شود پسوند .txt به آن اضافه میکنند. ویندوز به طور پیشفرض پسوندها را نشان نمی دهد ولی تنظیماتی دارد برای نمایششون. در نتیجه بعد از ایجاد فایل حتما باید پسوند را از فایل حذف کنید.
!INCLUDE $(NTMAKEENV)\makefile.def
فایل بعدی sources است که باز این فایل هم مانند فایل قبلی پسوند ندارد. این فایل تمام اطلاعات لازم برای ایجاد درایور شما توسط ابزار build را دربر می گیرد.
TARGETNAME = hello TARGETPATH = obj TARGETTYPE = DRIVER INCLUDES = %BUILD%\inc LIBS = %BUILD%\lib SOURCES = hello.c
خوب از اطلاعات بالا
TARGETNAME نام درایور شما موقع ایجاد توسط build است
TARGETPATH پوشه ای که درایور داخلش قرار می گیرد.
TARGETTYPE نوع فایلی که قرار است ایجاد شود که خوب از آنجایی که ما درایور می سازیم واضح است که نوع هم DRIVER است
INCLUDES و LIBS به ابزار build می گوید کجا دنبال هدرها و کتابخانه های مورد نیاز بگردد که خیلی مهم نیست برامون
SOURCES: نام فایل سورس ما اینجا قرار می گیرد اگر بیشتر از یک فایل باشد باید نامها با فاصله از هم جدا شوند.
فایلی به نام hello.c هم میسازیم با این محتویات
#include <ntddk.h>
VOID Unload(IN PDRIVER_OBJECT pDriverObject)
{
DbgPrint("Driver Unloaded.\n");
return;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING regPath)
{
DbgPrint("Hello World!.\n");
pDriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
build کردن کد
قبل اینکه توضیحی در مورد کد بدهم نحوه biuild کردنش را می گویم. برای build کردن باید از منوی start و پوشه ای که مربوط به WDK است x86 Free Build را انتخاب کنید.یک صفحه cmd برایتان باز می شود به مسیر پروژه ای که ایجاد کرده اید بروید
C:\WinDDK\7600.16385.1>cd C:\Users\[your_user]\src\Drivers\hello
بعد دستور build را وارد کنید
C:\Users\[your_user]\src\Drivers\hello>build
این دستور کمی زمان می برد بعد اگر مشکلی نباشد درایور شما در پوشه ای به نام objfre_wxp_x86\i386 یا اگر نام پوشه را تغییر داده اید در پوشه مربوطه قرار می گیرد
می توان کمی روند build رو با وارد کردن پارامتر Z تصریح کرد، من پارامتر g و c را هم برای رنگی کردن پیامها و پاک کردن آبجکت قبلی وارد میکنم. که به این صورت می شود
C:\Users\[your_user]\src\Drivers\hello>build /gcZ
توضیح در مورد کد
اگر فردی باشید که با C بیشتر برنامه های کنسولی نوشته باشید، چه برای دل خودتون یا پروژه های دانشگاهی یا پروژه های کاری و با اصطلاحاتی مانند Win32 API, DLL, MFC و غیره سر و کار نداشته اید. احتمالا این کد برایتان کمی عجیب خواهد بود. کدی که نه تابع main دارد و نه از انواع داده های استاندار C خبری است. کلا در دنیای ویندوز این روال همیشه اینگونه بوده و ماکروسافت علاقه زیادی دارد که همه چی را با عناوین دیگری تعریف کند. در واقع برای تمام انواع داده ها عناوین دیگری تعریف کرده است که به مرور با آنها آشنا خواهید شد برای نمونه در کد بالا برای void نوع جدید VOID با حروف بزرگ تعریف شده است. لازم به ذکر است در کنار این داده های جدید شما همچنان می توانید از تمام امکانات زبانی زبان C استفاده کنید.
درایور هم مانند برنامه های کنسولی که نوشته اید دارای تابعی مانند main است که بعد از لود شدن درایور صدا زده می شود. این تابع DriverEntry نام دارد. که به صورت زیر تعریف شده
NTSTATUS DriverEntry(
_In_ struct _DRIVER_OBJECT *DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{ ... }
این تابع دو پارامتر ورودی می گیرد، توضیحات بیشتر این تابع را در سایت ماکروسافت ببینید
DriverObject: ساختاری است که مقادیر متعددی دارد که در آموزش های بعدی در موردشان بیشتر توضیح خواهم دارد. در اینجا ما فقط از مقدار DriverUnload در این ساختار استفاده کرده ایم. این مقدار به عنوان ورودی نام تابعی را می گیرد که در هنگام Unload شدن درایور صدا زده می شود. نکته ای که باید به آن توجه کرد این است که اگر تابعی برای Unload تعیین نکنیم درایور ما بعد از لود شدن دیگر Unload نمی شود.
RegistryPath: این پارامتر مسیر درایور ما در رجیستری بعد ثبت شدن را در خود دارد که برای ما کاربردی ندارد و فعلا از آن صرف نظر میکنم، خوب است خودتان در مورد نوع PUNICODE_STRING کمی تحقیق کنید ببینید به چه صورت تعریف شده است.
مقدار برگشتی: مقداری که تابع DriverEntry بر می گرداند باید از نوع NTSTATUS باشد. این نوع داخل فایل ntstatus.h تعریف شده و مقادیر بسیار زیادی دارد. ما در این مثال مقدار STATUS_SUCCESS برگردانده ایم. به این معنی که درایور بدون مشکلی لود شده است. برای دیدن تمام مقادیر این صفحه را ببینید
حالا میرسیم به چاپ یک متن مانند "سلام دنیا"، خوب از آنجایی که دنیای کرنل زمین تا آسمان با برنامه هایی که در سطحی که به آن User-Mode یا سطح کاربر گفته می شود فرق دارد. چاپ کردن یک پیام روی صفحه مانند سطح کاربر که یک متن با printf روی کنسول بفرستیم نیست یعنی اصلا همچین چیزی نداریم، پس حتما می گویید ما را سره کار گذاشته ای با این آموزشت (شاید). در واقع قابلیتی وجود دارد که با استفاده از توابع DbgPrint یا KdPrint و امثالهم می توان پیامی به بافری فرستاد که بعد یکسری ابزار هستن که می تواند این بافر را بخواند. این ابزار برای مثال می تواند یک دیباگر یا ابزاری مثل DbgView باشد. همانطوری هم که در کد می بینید ما در دو قسمت از DbgPrint استفاده کرده ایم یکی در تابع DriverEntry برای چاپ "Hello World!\n" و دیگری در تابع Unload . متنی که قرار می دهید می تواند فرمتی مشابه printf در C داشته باشد، در ضمن می توانید همراه متن کاراکترهای کنترلی مثل "n\" هم بفرستید تا پیام ها در هنگام نمایش در خطوط مجزا قرار گیرند برای اطلاعات بیشتر هم می توانید صفحه مربوط به تابع در سایت ماکروسافت را ببینید.
نکته: در ویندوزهای بعد از ویستا تابع DbgPrint چیزی را به بافر دیباگ نمی فرستد و باید از تابعی جایگزین که DbgPrintEx است استفاده کنید. به این صورت
DbgPrintEx(DPFLTR_DEFAULT_ID,
DPFLTR_ERROR_LEVEL,
"YOUR MESSAGE");
ثبت و لود درایور
بعد از اینکه درایور ایجاد شد باید لودش کنیم. برای لود کردن درایور راه های مختلفی وجود دارد، یک راه استفاده از ابزارهای آماده برای این منظور است که من یک نمونه آن را در این آموزش آورده ام به نام OSRLoader و راه دیگر استفاده از API هایی است که ماکروسافت در ویندوز قرار داده تا با آنها بتوان یک درایور را لود کرد (البته راه هایی دیگر و غیر مستند دیگری وجود دارد که فعلا به آن اشاره نمیکنم). مسلما اگر پروژه ای داشته باشید و بخواهید با برنامه نویسی درایوری لود کنید باید از API های ویندوز استفاده کنید. داخل نمونه سورس هایی که همراه WDK است نمونه کد برای این منظور زیاد است. من اینجا نحوه استفاده از OSRLoader را میگویم. در زیر تصویری از محیط این ابزار را می توانید مشاهده کنید
در این محیط ما تنظیمات خاصی نمی خواهیم انجام بدهیم فقط درایور خود را از طریق دکمه Browse انتخاب کنید. در آموزش های بعدی بیشتر در مورد این تنظیمات صحبت میکنم.
به طور کلی دو مرحله است که باید طی کنیم تا یک درایور لود شود، اول درایور باید در سیستم ثبت شود. برای این کار بعد انتخاب درایور روی دکمه Register Service کلیک کنید. این کار باعث می شود کلیدی در رجیستری سیستمتون در مسیر زیر همنام با اسم درایور ایجاد شود. که در مثال ما نام کلید hello می باشد.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
حالا که درایور ثبت شده می توان درایور را اجرا کرد. ولی قبل از اجرا کردن ابزار DbgView که بالا لینک دانلودش را قرار داده ام بگیرین و اجرا کنید. و بعد از باز کردن ابزار از منوی Capture گزینه Capture Kernel را انتخاب کنید که یک تیک کنارش بخورد. با این کار این ابزار پیامهایی که در سطح کرنل با استفاده از تاابع DbgPrint فرستاده می شود را دریافت میکند. حالا با کلیک بر دکمه Start Service درایور اجرا می شود و شما باید پیامهایی که در درایورتون با DbgPrint گداشته اید ببینید.
بعد برای اتمام کار درایور و متوقف کردن آن به ترتیب اول دکمه Stop Service و بعد Unregister Service را کلیک کنید. در این مرحله نیز پیامی که در تابع Unload قرار داده اید باید نمایش داده شود.
خوب رسیدیم به پایان این آموزش، امیدوارم براتون مفید واقع شده باشد. اگر ایراد، ابهام یا هر سوالی دارید حتما بپرسید این کار را هم از طریق کامنت یا از طریق قسمت "تماس با من" می توانید مطرح کنید