C#嵌入x86匯編——一個GPIO接口的實現
開始進入工業自動化,買的工控機帶有GPIO接口,可用于直接控制繼電器。
從廠家拿到接口手冊一看,居然是匯編直接操作端口,基本上是IN/OUT指令了。接口很簡單,計算位移,讀取;計算位移,寫入。
這種接口,常見有四種辦法,分別是四種語言實現,一是直接寫ASM,不過要公開給C#做的應用程序調用,很不容易,另外三種是C/C++/Delphi嵌入匯編,倒是問題不大。
接口實在是小,不想大動干戈,所以想了別的辦法。
第五種,用C++/CLI,這也是一個不錯的主意。但是我甚至想省掉這個接口DLL,于是有了第六種辦法:C#嵌入x86匯編。
C#是沒辦法像C/C++/Delphi那樣直接嵌入x86匯編的,所以需要做點手腳。
在匯編里面,我們為了修改一個軟件經常找一塊空白區域來寫匯編代碼,然后Jmp過去執行。(不明白這一句話的可以跳過,或者去看雪論壇)
但是顯然要在C#代碼里面這么做很不現實,即使用C/C++編譯得到obj,C#也沒辦法鏈接這個obj。(這個涉及編譯的也可以跳過)
回頭一想(其實不是現在想,07年就做過C#嵌入匯編),其實C#也跑在x86上,IL指令最終還是要編譯成x86匯編指令的,我們應該可以這些寫匯編指令,所需要的只是一塊空間而已。
我們可以申請一塊非托管空間嘛,于是有:
// 分配內存
var ptr = Marshal.AllocHGlobal(code.Length);
有了空間,我們就可以把二進制的匯編指令給寫進去啦:
// 寫入匯編指令Marshal.Copy(code, 0, ptr, code.Length);
然后呢?.Net提供一個途徑,讓我們可以把一個內存指針轉為一個委托(一直都說.Net的委托其實就是C/C++的函數指針哈):
// 轉為委托return(T)(Object)Marshal.GetDelegateForFunctionPointer(ptr,typeof(T));
那么,剩下的問題,就是如何把匯編轉為二進制了!
這個我們是不能像C/C++/Delphi那樣直接寫匯編指令的,所以得走點彎路。
我的做法是用OD隨便打開一個程序,在上面直接寫匯編代碼,然后把匯編的十六進制復制出來,放到C#代碼中。
剩下的就不多說了,直接上代碼吧!
GPIO接口實現
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; namespace ConsoleApplication19 { class GPIO { #region 屬性 private Int32 _Offset; /// <summary>選擇位移</summary> public Int32 Offset { get { return _Offset; } set { _Offset = value; } } private Int32 _Bit; /// <summary>選擇位</summary> public Int32 Bit { get { return _Bit; } set { _Bit = value; } } #endregion #region 構造 private GPIO(Int32 offset, Int32 bit) { Offset = offset; Bit = bit; } private GPIO(Int32 gpio) { Offset = gpio / 16; Bit = gpio % 16; } #endregion #region 預定義針腳 public static GPIO Pin2 = new GPIO(0, 6); public static GPIO Pin3 = new GPIO(0, 7); public static GPIO Pin4 = new GPIO(2, 1); public static GPIO Pin5 = new GPIO(2, 4); public static GPIO Pin6 = new GPIO(1, 0); public static GPIO Pin7 = new GPIO(1, 4); public static GPIO Pin8 = new GPIO(3, 3); public static GPIO Pin9 = new GPIO(3, 4); public static GPIO IO6 = new GPIO(6); public static GPIO IO7 = new GPIO(7); public static GPIO IO17 = new GPIO(17); public static GPIO IO20 = new GPIO(20); public static GPIO IO8 = new GPIO(8); public static GPIO IO12 = new GPIO(12); public static GPIO IO27 = new GPIO(27); public static GPIO IO28 = new GPIO(28); #endregion #region 業務 /// <summary>是否啟用</summary> public Boolean Enable { get { return Read(Offset, Bit); } set { WriteBit(Offset, Bit, value); } } /// <summary>是否輸出</summary> public Boolean Output { get { return Read(Offset + 4, Bit); } set { WriteBit(Offset + 4, Bit, value); } } /// <summary>是否設置數據位</summary> public Boolean Data { get { return Read(Offset + 12, Bit); } set { WriteBit(Offset + 12, Bit, value); } } #endregion #region 讀取端口 const Int16 BASEADDRESS = 0x500; Boolean Read(Int32 offset, Int32 bit) { var d = ReadHandler((Int16)(BASEADDRESS + offset)); var c = (Byte)~(1 << bit); d &= c; return d == c; } private static ReadFunc _ReadHandler; /// <summary>屬性說明</summary> public static ReadFunc ReadHandler { get { return _ReadHandler ?? (_ReadHandler = GetReadHandler()); } } //static IntPtr ptr; static ReadFunc GetReadHandler() { // 匯編指令 var code = new Byte[] { 0x66, 0x8B, 0x55, 0x08, //mov dx, word ptr [ebp+8] 0xEC, //in al, dx }; return (ReadFunc)InjectASM<ReadFunc>(code); } public delegate Byte ReadFunc(Int16 address); #endregion #region 寫入端口 void Write(Int32 offset, Int32 value) { WriteHandler((Int16)(BASEADDRESS + offset), (Byte)value); } private static WriteFunc _WriteHandler; /// <summary>屬性說明</summary> public static WriteFunc WriteHandler { get { return _WriteHandler ?? (_WriteHandler = GetWriteHandler()); } } static WriteFunc GetWriteHandler() { // 匯編指令 var code = new Byte[] { 0x66, 0x8B, 0x55, 0x08, //mov dx, word ptr [ebp+8] 0x8A, 0x45, 0x0C, //mov al, byte ptr [ebp+C] 0xEE //out dx, al }; return InjectASM<WriteFunc>(code); } public delegate void WriteFunc(Int16 address, Byte bit); #endregion #region 寫入端口位 void WriteBit(Int32 offset, Int32 bit, Boolean value) { if (value) SetBitHandler((Int16)(BASEADDRESS + offset), (Byte)bit); else ClearBitHandler((Int16)(BASEADDRESS + offset), (Byte)bit); } private static WriteBitFunc _SetBitHandler; /// <summary>設置位</summary> public static WriteBitFunc SetBitHandler { get { return _SetBitHandler ?? (_SetBitHandler = GetSetBitHandler()); } } private static WriteBitFunc _ClearBitHandler; /// <summary>清除位</summary> public static WriteBitFunc ClearBitHandler { get { return _ClearBitHandler ?? (_ClearBitHandler = GetClearBitHandler()); } } static WriteBitFunc GetSetBitHandler() { // 匯編指令 var code = new Byte[] { 0x53, //push ebx 0x51, //push ecx 0x66, 0x8B, 0x55, 0x08, //mov dx, word ptr [ebp+8] 0x8A, 0x45, 0x0C, //mov al, byte ptr [ebp+C] 0xB3, 0x01, //mov bl, 1 0xD2, 0xE3, //shl bl, cl 0xEC, //in al, dx 0x08, 0xD8, //or al, bl 0xEE, //out dx, al 0x59, //pop ecx 0x5B //pop ebx }; return InjectASM<WriteBitFunc>(code); } static WriteBitFunc GetClearBitHandler() { // 讀出字節,取消指定位后重新寫回去 var code = new Byte[] { 0x53, //push ebx 0x51, //push ecx 0x66, 0x8B, 0x55, 0x08, //mov dx, word ptr [ebp+8] 0x8A, 0x45, 0x0C, //mov al, byte ptr [ebp+C] 0xB3, 0x01, //mov bl, 1 0xD2, 0xE3, //shl bl, cl 0xF6, 0xD3, //not bl 0xEC, //in al, dx 0x20, 0xD8, //and al, bl 0xEE, //out dx, al 0x59, //pop ecx 0x5B, //pop ebx }; return InjectASM<WriteBitFunc>(code); } public delegate void WriteBitFunc(Int16 address, Byte bit); #endregion #region 注入匯編 static T InjectASM<T>(Byte[] code) { // 匯編指令 var code1 = new Byte[] { 0x55, //push ebp 0x8B, 0xEC, //mov ebp, esp 0x52, //push edx }; var code2 = new Byte[] { 0x5A, //pop edx 0x8B, 0xE5, //mov esp, ebp 0x5D, //pop ebp 0xC3 //ret }; //var cbs = new Byte[code1.Length + code.Length + code2.Length]; var ms = new MemoryStream(); ms.Write(code1, 0, code1.Length); ms.Write(code, 0, code.Length); ms.Write(code2, 0, code2.Length); code = ms.ToArray(); // 分配內存 var ptr = Marshal.AllocHGlobal(code.Length); // 寫入匯編指令 Marshal.Copy(code, 0, ptr, code.Length); // 設為可執行 VirtualProtectExecute(ptr, code.Length); Console.WriteLine("0x{0:X8}", ptr.ToInt32()); Console.ReadKey(true); // 轉為委托 return (T)(Object)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)); } #endregion #region 輔助 //[DllImport("kernel32.dll", SetLastError = true)] //static extern int VirtualQueryEx(int hProcess, ref object lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength); [DllImport("kernel32.dll", SetLastError = true)] static extern int VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, int flNewProtect, ref int lpflOldProtect); static Boolean VirtualProtectExecute(IntPtr address, Int32 size) { const Int32 PAGE_EXECUTE_READWRITE = 0x40; Int32 old = 0; return VirtualProtectEx(Process.GetCurrentProcess().Handle, address, size, PAGE_EXECUTE_READWRITE, ref old) == 0; } #endregion } }
這里有個舊版本:http://bbs.53wb.com/forum.php?mod=viewthread&tid=137



浙公網安備 33010602011771號