8 Eylül 2018 Cumartesi

Offset Kavramı ve Dinamik Dizi Yapımı

Birçok programlama dilinde dinamik dizi kullanabiliyoruz.Dizilerde [] indexer operator yardımıyla dizi elemanlarına erişebiliyoruz.
C++
   int* myArray = new int[2];
   myArray[0] = 10;
   delete[] myArray;
C#
   int[] myArray = new int[2];
   myArray[0] = 10;
Java
   int[] myArray = new int[2];
   myArray[0] = 10;
Delphi
var
  MyArray: array of Integer;
begin
  SetLength(MyArray, 2);
  MyArray[0] := 10;
end.
Yukarıdaki örnekleri incelediğimizde C++, C#, Java gibi dillerde new operatörü, delphi için ise SetLength fonksiyonu yardımıyla dinamik dizileri kullanabiliyoruz.

C dilinde ise; C++, C#, java da bulunan new operatoru ya da delphi'deki gibi bir SetLength fonksiyonu bulunmuyor.Dolayısı ile dinamik dizimizi kendimiz gerçekleştirmemiz gerekiyor.
Aşağıdaki örnekte; 8 byte'lık bir hafıza bloğu tahsis edilip, myArray işaretçisinin [] indexer operatörü yardımıyla değerlere erişiyoruz.
 int* myArray = malloc(sizeof(int) * 2);
 myArray[0] = 10;
 myArray[1] = 20;
 free(myArray);
Sanırım nihayet konuya giriş yapabilmek için gerekli alt yapıyı sağlamış oldum :)
Peki dil C, C++, C#, java ya da delphi ya da bir başka dil farketmeksizin myArray[index] operatorü ile dizi elemanlarına erişmek istediğimde, hafıza bloğunda ilgili index'deki veriye nasıl eriştiğini biliyor muyum ? Bilmiyorum.

Bu yazıda dil seviyesinde index mantığını nasıl gerçekleştirebiliriz bunu anlamaya ve öğrenmeye çalışacağız. Bilindiği üzere C, C++, C#, Delphi gibi dillerde, işaretçileri(pointer), işaretçi aritmetiğini(pointer math) kullanabiliyoruz.Java'da ise yine bildiğim kadarıyla şuan için işaretçi kullanımına dil seviyesinde izin verilmiyor.

İşaretçi(pointer) kullanımına izin veren dillerde index'leme mantığını işaretçiler üzerinden verinin adresini hesaplayarak gerçekleştireceğiz. Dinamik bellek yöneten fonksiyonlar veriyi hafızada tahsis ettikten sonra geriye, veri bloğunun başlangıç adresini untyped pointer(void* veya PVOID veya LPVOID veya Pointer veya IntPtr) tipinde döndürmektedir. C
void *        _RTLENTRY _EXPFUNC malloc(_SIZE_T __size);
Windows API
WINBASEAPI
_Ret_maybenull_
_Post_writable_byte_size_(dwBytes)
DECLSPEC_ALLOCATOR
LPVOID
WINAPI
HeapAlloc(
    _In_ HANDLE hHeap,
    _In_ DWORD dwFlags,
    _In_ SIZE_T dwBytes
    );
C#
IntPtr p = Marshal.AllocHGlobal(size);
Delphi
procedure GetMem(var P: Pointer; Size: Integer);
function HeapAlloc(hHeap: THandle; dwFlags, dwBytes: DWORD): Pointer; stdcall;
UnTyped Pointer'larda aritmetik işlemlere izin verilmediği için, fonksiyonlardan dönen veri tipini typed pointer'lara çevireceğiz. Örneğin C dilinde aşağıdaki gibi untyped pointer tipinde ki p değişkeni için p += 4; işlemine izin verilmeyecektir.
void* p = malloc(sizeof(int) * 2);
p += 4;
[bcc32 Error] main.c(17): E2453 Size of the type 'void' is unknown or zero
Error C2036 'void *': unknown size

Örneğin Delphi dili için aşağıdaki gibi untyped pointer tipide ki P := P + 4; ya da Inc(P,4); işlemine izin verilmeyecektir.
var
  P: Pointer;
begin
  GetMem(P, SizeOf(Integer) * 2);
  P := P + 4;
end.
[dcc32 Error] Project2.dpr(60): E2015 Operator not applicable to this operand type
ya da
var
  P: Pointer;
begin
  GetMem(P, SizeOf(Integer) * 2);
  Inc(P, 4);
end.
[dcc32 Error] Project2.dpr(70): E2001 Ordinal type required
Dolayısı ile hafıza adreslerini index yoluyla hesaplayabilmemiz için typed pointerları kullanmamız gerekiyor.
C
 int* p = malloc(sizeof(int) * 2);
 *p = 10;
  p += 4;
 *p = 20;
free(p);
Delphi
var
  P: ^Integer; // ya da PInteger
begin
  GetMem(P, SizeOf(Integer) * 2);
  P^ := 10;
  Inc(P, 4);
  P^ := 20;
  FreeMem(P, SizeOf(Integer) * 2);
end;
C#
public static IntPtr Add(IntPtr pointer, int offset);
C#'da ise IntPtr(Pointer ya da Handle veri tipi için) yapısında bulunan Add methodu yardımıyla hesaplama yapabiliyoruz.
static void Main(string[] args)
{
  IntPtr p = Marshal.AllocHGlobal(sizeof(int) * 2);
  Marshal.WriteInt32(p, 10);
  IntPtr newP = IntPtr.Add(p, 4);
  Marshal.WriteInt32(newP, 20);
  Marshal.FreeHGlobal(p);
}
Yukarıdaki gibi hafıza bloğunun başlangıç adresini gösteren bir işaretçi üzerinde "doğrudan" değişiklik yapan p += 4; ya da P := P + 4; gibi aritmetik işlemler yazacağımız index'leme mantığı için çok doğru olmayabilir.O nedenle dinamik bellekte veriye işaret eden işaretçi üzerinde doğrudan bir değişiklik yapmadan yeni adresi hesaplayabiliriz.

Basit bir hesap ile formülümüz aşağıdaki gibi olacaktır.
Kaç Bayt = index X Veri tipinin kapladığı alan(Byte)
Yeni Adres = Başlangıç adresi + Kaç Byte

Kabaca başlangıç adresine kaç byte ekleyeceğimizi index yoluyla hesaplayarak verinin offset'ini hesaplamış olduk. Formülü kodlayacak olursak;
C
int index = 0;
int* p = malloc(sizeof(int) * 2);
int* newp = p + (index * sizeof(int));
*newp = 10;
free(p);
Delphi
var
  P, NewP: PInteger;
  Index: Integer;
begin
  Index := 0;
  GetMem(P, SizeOf(Integer) * 2);
  NewP := PInteger(PByte(P) + (Index * SizeOf(Integer)));
  NewP^ := 10;
  FreeMem(P, SizeOf(Integer) * 2);
end;
index değişkeninin değerini değiştirerek dizinin elemanlarına erişim sağlayabiliriz. Yazdığımız kodları eleştirecek olursak; Kodumuz çok da yakışıklı olmadı.
Örneğin; index * sizeof(int) yerine bir offset fonksiyonu yazabiliriz. C
int Offset(int index){
  return index * sizeof(int);
}
int* newp = p + Offset(index);
Delphi
function Offset(Index: Integer): Integer;
begin
  Result := Index * SizeOf(Integer);
end;
NewP := PInteger(PByte(P) + Offset(Index));
Dizinin eleman sayısından, hafızada tahsis edilecek veri bloğununun kaplayacağı alanı hesaplayan bir fonksiyon yazılabilir.
C
int* p = malloc(sizeof(int) * 2);
yerine
int MemSize(int count) {
   return sizeof(int) * count;
}
int* p = malloc(MemSize(2));
Delphi
GetMem(P, SizeOf(Integer) * 2); 
yerine
function MemSize(Count: Integer): Integer;
begin
  Result := SizeOf(Integer) * Count;
end;
GetMem(P, MemSize(2));
Eleştirmeye devam edecek olursak;
Şuana kadar yazdığımız kodların tamamı int veri tipi üzerine idi. Programlama sadece int veri tipinden ibaret değil ki ?
Başka veri tipleri için ne yapacağız ?
Aynı mantığı her veri tipi için tekrar tekrar mı yazacağız ?

İşte tam bu noktada, algoritmayı veri tipinden soyutlamak ya da bağımsız kılmak için, nesne yönelimli programlamanın sağladığı faydalardan yararlanabiliriz. Bunun için C++'da templates, C# ve Delphi'de generics'ler ile daha esnek kodlamalar yapabiliriz.Generics ve templates ile her ne kadar veri tipini esnek hale getirmiş olsak da, algoritmamız her veri tipi için uygun olmayabilir.Bu nokta da generics constraint'ler ile belli tipler için kurallar getirebiliriz. Tabi ki yine C'de untyped pointerlar ile daha generic fonksiyonlar mümkün.

Bahsettiğim generic haline Dynamic Array Implementation adresinden erişebilir, katkı sağlayabilir (mümkünse), gördüğünüz eksik ya da yanlışlar konusunda bilgilendirirseniz sevinirim.

Sağlıcakla.

1 Eylül 2018 Cumartesi

Fonksiyon Çağırım Düzeni/Biçimi/Şekli Ya da Her Neyse

Fonksiyon çağırım düzeni (Calling Convention kısaca CC diyelim) özetle;
fonksiyon ya da procedure parametrelerinin hangilerinin CPU register veya stack'de, hangi sıra ile tahsis edileceğidir.

Delphi, C/C++ gibi dillerde fonksiyon ya da procedure'e ekleyeceğimiz CC ile (register, pascal, cdecl, stdcall, safecall, winapi, __fastcall gibi) fonksiyonun çağırım düzeni belirtilebilir.

Delphi'de varsayılan CC register'dır. Yani fonksiyon ya da procedure'nin sonuna herhangi CC belirtilmediğinde varsayılan olarak register çalışır.Stack frame oluşturmak yerine, ECX, EDX, EAX gibi CPU register'ları kullandığı için, fonksiyonun çalışma hızı en efektif ve hızlı olanıdır.Aşağıdaki fonksiyonda X ve Y parametreleri stack yerine CPU register'larda tahsis edilecektir.
function Hesapla(X, Y: Integer): Integer; 
function Hesapla(X, Y: Integer): Integer; register;
Yukarıdaki her iki fonksiyon da aynı CC sahiptir diyebiliriz.
C/C++'da varsayılan CC cdecl'dir. cdecl adı üstünde c decleration kısaltmasıdır.Tüm parametreler stack'de tutulur. C/C++
int Hesapla(int X, int Y);
int __cdecl Hesapla(int X, int Y);  
Delphi
function Hesapla(X, Y: Integer): Integer; cdecl;
Yukarıdaki bilgilere göre;
Varsayılan bir C fonksiyonu ile, varsayılan bir delphi fonksiyonunu karşılaştırdığımızda, mantıken ve de teknik olarak delphi fonksiyonun daha hızlı çalışması gerekir.
Çünkü C'de "varsayılan olarak", fonksiyon parametreleri stack'de tahsis ediliyorken, delphi'de 3 adet parametreye kadarı CPU register'larda geriye kalanlar ise stack'de tahsis edilir. CPU register'ları stack'den daha hızlıdır.

C'de delphi'de olduğu gibi parametreleri stack yerine CPU register'da tahsis etmek için __fastcall CC kullanılır. Kısaca C'nin __fastcall'ı ile Delphin'nin register CC'ı aynıdır diyebiliriz. Delphi
function Hesapla(X, Y: Integer): Integer; register;
begin
end;
Hesapla(1,2);
ASM Çıktısı
Project1.dpr.45: Hesapla(1,2);
00419500 BA02000000       mov edx,$00000002
00419505 B801000000       mov eax,$00000001
0041950A E881DDFFFF       call Hesapla
C/C++
int __fastcall Hesapla(int X, int Y) {
}
Hesapla(1,2);
ASM Çıktısı
main.cpp.48: Hesapla(1,2);
0040125B BA02000000       mov edx,$00000002
00401260 B801000000       mov eax,$00000001
00401265 E8DEFFFFFF       call Hesapla(int,int)
Dokümanlar winapi ya da stdcall'ın aslında bir CC olmadığını söylüyor.
Daha çok windows işletim sistemindeki API'lerde bu CC görebiliriz. winwindef.h içerisinde tanımı şöyle;
#define WINAPI __stdcall
Örneğin CreateFile fonksiyonun tanımında görebiliriz. fileapi.h
WINBASEAPI
HANDLE
WINAPI
CreateFileA(
   _In_ LPCSTR lpFileName,
   _In_ DWORD dwDesiredAccess,
   _In_ DWORD dwShareMode,
   _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
   _In_ DWORD dwCreationDisposition,
   _In_ DWORD dwFlagsAndAttributes,
   _In_opt_ HANDLE hTemplateFile
   );
Delphi zaten bir çok windows api fonksiyonunu hali hazırda bize WinApi.Windows.pas uniti içersinde sunuyor.
function CreateFileA(lpFileName: LPCSTR; dwDesiredAccess, dwShareMode: DWORD;
 lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
 hTemplateFile: THandle): THandle; stdcall;
function CreateFileA; external kernelbase name 'CreateFileA';
Yine dokümanlar pascal CC'nin ise geriye dönük uyumluluk için korumaya devam edildiğini söylüyor. Şimdi örnek procedure ya da fonksiyonların CPU penceresindeki kodunu inceleyelim.
(View -> Debug Windows -> CPU Windows -> Entire CPU) ya da CTRL + ALT + C
program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses

 System.SysUtils;

procedure Procedure1(A, B, C, D, E: Integer);
begin
end;

procedure Procedure2(A, B, C, D, E: Integer); register;
begin
end;

procedure Procedure3(A, B, C, D, E: Integer); pascal;
begin
end;

procedure Procedure4(A, B, C, D, E: Integer); cdecl;
begin
end;

procedure Procedure5(A, B, C, D, E: Integer); stdcall;
begin
end;

procedure Procedure6(A, B, C, D, E: Integer); safecall;
begin
end;

begin
 Procedure1(1, 5, 7, 9, 10);
 Procedure2(1, 5, 7, 9, 10);
 Procedure3(1, 5, 7, 9, 10);
 Procedure4(1, 5, 7, 9, 10);
 Procedure5(1, 5, 7, 9, 10);
 Procedure6(1, 5, 7, 9, 10);
end.
Project1.dpr.40: Procedure1(1, 5, 7, 9, 10);
00419500 6A09             push $09
00419502 6A0A             push $0a
00419504 B907000000       mov ecx,$00000007
00419509 BA05000000       mov edx,$00000005
0041950E B801000000       mov eax,$00000001
00419513 E878DDFFFF       call Procedure1
Project1.dpr.41: Procedure2(1, 5, 7, 9, 10);
00419518 6A09             push $09
0041951A 6A0A             push $0a
0041951C B907000000       mov ecx,$00000007
00419521 BA05000000       mov edx,$00000005
00419526 B801000000       mov eax,$00000001
0041952B E878DDFFFF       call Procedure2
Project1.dpr.42: Procedure3(1, 5, 7, 9, 10);
00419530 6A01             push $01
00419532 6A05             push $05
00419534 6A07             push $07
00419536 6A09             push $09
00419538 6A0A             push $0a
0041953A E881DDFFFF       call Procedure3
Project1.dpr.43: Procedure4(1, 5, 7, 9, 10);
0041953F 6A0A             push $0a
00419541 6A09             push $09
00419543 6A07             push $07
00419545 6A05             push $05
00419547 6A01             push $01
00419549 E87ADDFFFF       call Procedure4
0041954E 83C414           add esp,$14
Project1.dpr.44: Procedure5(1, 5, 7, 9, 10);
00419551 6A0A             push $0a
00419553 6A09             push $09
00419555 6A07             push $07
00419557 6A05             push $05
00419559 6A01             push $01
0041955B E870DDFFFF       call Procedure5
Project1.dpr.45: Procedure6(1, 5, 7, 9, 10);
00419560 6A0A             push $0a
00419562 6A09             push $09
00419564 6A07             push $07
00419566 6A05             push $05
00419568 6A01             push $01
0041956A E869DDFFFF       call Procedure6
0041956F E8C4F1FEFF       call @CheckAutoResult
Project1.dpr.46: end.
main.cpp
#pragma hdrstop

#pragma argsused

#ifdef _WIN32

#include 

#else

typedef char _TCHAR;

#define _tmain main

#endif

#include 

void Procedure1(int A, int B, int C, int D, int E) {

}

void __fastcall Procedure2(int A, int B, int C, int D, int E) {

}

void pascal Procedure3(int A, int B, int C, int D, int E) {

}

void __cdecl Procedure4(int A, int B, int C, int D, int E) {

}

void __stdcall Procedure5(int A, int B, int C, int D, int E) {

}

void Procedure6(int A, int B, int C, int D, int E) {

}

int _tmain(int argc, _TCHAR* argv[]) {

  Procedure1(1, 5, 7, 9, 10);
  Procedure2(1, 5, 7, 9, 10);
  Procedure3(1, 5, 7, 9, 10);
  Procedure4(1, 5, 7, 9, 10);
  Procedure5(1, 5, 7, 9, 10);
  Procedure6(1, 5, 7, 9, 10);
  return 0;
}

Yukarıdaki çıktıları incelediğimizde, parametrelerin hangi sırayla (sağdan sola veya soldan sağa doğru) stack'e ittirildiğini(push) ya da hangi CPU register'da (ECX,EDX, EAX) tahsis edildiğini somut bir şekilde görebiliriz. Delphi, C/C++ gibi dillerde fonksiyonlarda çağırım düzenini kısaca bu şekilde ifade edebiliriz.

.net dillerinde ise fonksiyon çağırım düzeni, DLLImport attribute sınıfında bulunan CallingConvention özelliği kullanarak belirtilebilir.Bu tür managed dillerden, umanaged system fonksiyonlarını çağırmanın diğer bir adı ise Platform Invoke ya da kısaca P/Invoke diye ifade edilir.
[DllImport("User32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, int uType);
static void Main(string[] args)
{
   MessageBox(IntPtr.Zero, "Bu bir mesajdır", "Bu da bir başlıktır", 0);
}
Java'da ise "bildiğim kadarıyla" "dil seviyesinde" .net'deki DllImport'a benzer bir sınıf yok. Aşağıdaki kaynaklara göz atabilirsiniz.
x86 calling conventions
Calling Conventions
Pitfalls of converting
Delphi Language Guide (Delphi for Microsoft Win32 Delphi for the Microsoft .NET Framework)

21 Ekim 2017 Cumartesi

Windows Sürücü Programlama Ve Hata Ayıklama

Fikir Sahibi Olmak İçin
Windows çekirdek (Kernel) programlamaya başlamadan önce, öğrenildiğinde ne gibi faydalar sağlayabileceği ve neler yapabileceğimiz ile ilgili kernelturk bloğunda bulunan blog yazısının okunması faydalı olabilir.

Gereksinimler
Windows çekirdek programlamaya başlamadan önce, C programlama dilini fonksiyon pointer'larını, callback fonksiyonları vs anlamak gerekiyor.Eğer bir user mod sürücüsü geliştireceksek C++ ve COM teknolojisini anlamak gerekiyor.

Programlama İçin Gerekli Araçlar
İlk olarak çalışma ortamını sağlamak adına, sanal makina(Virtual Box) içerisine bir windows işletim sistemi kuruyorsunuz.Tercihen windows 10 kurulabilir.Yine aynı sanal makina içerisine Visual studio, SDK, WDK ve OSR driver Loader yazılımlarını indirip kuruyorsunuz.Yakın zamanda Visual Studio 2017'nin driver geliştirme desteği bulunmuyordu.Visual Studio 2017 için driver geliştirme desteği yeni eklenmiş diyebilirim.Şuan için önerim sırayla Visual Studio Community 2015, SDK, WDK araçlarını araçlarını indirip kurmanızdır.

Sanal makina içerisine gerekli yazılımlar kurulduktan sonra, bu sanal makina içerisindeki işletim sistemini, işletim sisteminin çekirdeğinde (kernel) çalışan yazılımların(sürücü) doğru çalışıp çalışmadığını kontrol etmek, hata ayıklama yapabilmek için aynı zamanda ana makinamıza'da SDK araçlarını kuruyoruz.
Kurduğumuz SDK içerisinde yer alan windbg.exe ile sanal makinayı debug edeceğiz.
WinDbg programı C:\Program Files (x86)\Windows Kits\10\Debuggers\x64 veya C:\Program Files (x86)\Windows Kits\10\Debuggers\x86 klasörleri altında bulunabilir.
Buraya kadar geldiysek programlama yapabilmek için gerekli araçları sağlamış oluyoruz.


Ayarların Yapılması
WinDbg ile sanal makina üzerinde hata ayıklama yapabilmek için kurduğumuz sanal makina ayarlarında bulunan Seri Bağlantı Noktalarını aşağıda görüldüğü gibi aktifleştiriyoruz.





Yol/Adres bilgisini WinDbg ile sanal makinayı debug ederken kullanacağız.


Nihayet Sürücü Geliştirmeye Başlamak

Artık Visual studio ile sürücü geliştirmeye ve geliştireceğimiz sürücüyü sanal makinaya yüklemeye ve debug etmeye hazırız.
File -> New -> Project -> Kernel Mode Driver, Empty (KMDF) ile yeni bir proje açıyoruz.


Projeye mydriver.c isminde yeni bir kaynak kod dosyası ekliyoruz.(Add -> New Item)


Proje ayarlarındaki Driver Settings bölümünde bulunan değerler aşağıdaki olmalıdır.
Target OS Version = Windows 10
Target Platform = Desktop 

Projeyi derlediğinizde aşağıdaki gibi bir hata mesajı alırsanız;
Error C2220 warning treated as error - no 'object' file generated KMDFDriver1 

Projenin özelliklerinden Linker bölümünden "Treat Linker Warning As Error" değerini "No (/WX:NO)" olarak ve  C/C++ "Warning Level" değerini "Level1 (/W1)" olarak değiştirmeniz gerekecek.


Proje başarılı bir şekilde derlendiğinde aşağıdaki gibi bir derleyici çıktı mesajı alacağız.

Yukarıdaki mesajlardan projenin başarılı bir şekilde derlediğini ve Visual studio'nun otomatik olarak bir test sertifikası oluşturup bu sertifikayı bilgisayardaki sertifika deposuna eklediğini ve sonrasında da bu sertifika ile KMDFDriver1.sys PE formatında ki dosyayı imzaladığını anlıyoruz.


Oluşan sertifika ve sürücü dosyası aşağıdaki gibi olacaktır.Buradaki KMDFDriver1.sys dosyasına sağ tıklayıp özellikler Digital Signatures kısmından KMDFDriver1.cer test sertifikası ile imzalandığını görmek mümkündür.

Yukarıda derleme çıktısında bize oluşturulan test sertifikasının bilgisayardaki sertifika deposuna eklendiğini söylemişti.Kontrol etmek için komut satırından certmgr komutunu çalıştırarak, açılan sertifika deposundan kontrol edebiliriz.Visual studio içersinden oluşturulan bu test sertifikaları test için oluşturulmuş kod imzalama sertifikalarıdır.

Bu sertifikalar sadece test ortamında kullanılmak için oluşturulmuş olup, yazılan sürücüleri gerçek ortamda kullanabilmek için kod imzalama sertifikası üreten firmalardan bir tane satın alıp onunla imzalamak gerekiyor.

Proje ayarlarından Driver Signing bölümünden test ve gerçek ortam sertifika seçimleri yapılabilinir.


Bu sertifikalar sadece test ortamında kullanılmak için oluşturulmuş olup, yazılan sürücüleri gerçek ortamda kullanabilmek için kod imzalama sertifikası üreten firmalardan bir tane satın alıp onunla imzalamak gerekiyor.Proje ayarlarından Driver Signing bölümünden test ve gerçek ortam sertifika seçimleri yapılabilinir.

Sürücü Yükleme Ve Hata Ayıklama

Şimdi elimizde gerçek bir sertifika olmadığına göre mevcut test sertifikası ile imzalanmış driver dosyasını nasıl sanal makinaya yükleyip çalıştıracağız onu görelim.
Driver dosyasını (KMDFDriver1.sys) sisteme yükleme yapabilmek için yukarıda bahsettiğim OSR Driver Loader programını kullanacağız.

OSRLOADER.exe'yi çalıştırıp driver dosyasını (KMDFDriver1.sys) seçip sırayla Register Service ve Start Service butonları ile driver'ı yükleme işlemi gerçekleştirilir.Fakat sürücü imzalanmış olsa bile eğer bilgisayar Test Mod'da değilse Start Service butonuna tıkladığınızda aşağıdaki gibi bir hata mesajı alacaksınız.


[Window Title]
Program Compatibility Assistant
[Main Instruction]
A digitally signed driver is required
[Close]
---------------------------
OSRLOADER
---------------------------
Windows cannot verify the digital signature for this file. 
A recent hardware or software change might have installed a file that is signed incorrectly or damaged, 
or that might be malicious software from an unknown source.
---------------------------
OK   
---------------------------
Yapmamız gereken burada bahsedildiği gibi komut satırını yönetici olarak çalıştırıp, sonrasında aşağıdaki komutu çalıştırıp sanal makinayı yeniden başlatmak olacaktır.
bcdedit /set TESTSIGNING ON
Bu aşamadan sonra tekrar OSRLOADER.exe ile sürücüyü yüklemeyi deneyebilirsiniz.

Yazıda gördüğünüz eksik ya da yanlışları haber ederseniz sevinirim.