• 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
        }
    }

    image

    这里有个旧版本:http://bbs.53wb.com/forum.php?mod=viewthread&tid=137

    我不相信神话,我只相信汗水!我不相信命运,我只相信双手!
  • 相关阅读:
    设计模式总结
    字符编码小结
    搞定java.io
    将代码托管到GitHub上
    linuxlinux 路由表设置 之 route 指令详解 路由表设置 之 route 指令详解
    linux子系统的初始化_subsys_initcall()
    Linux系统目录结构介绍
    EtherType :以太网类型字段及值
    socket编程原理
    linux下的网络接口和网络桥接
  • 原文地址:https://www.cnblogs.com/nnhy/p/2493761.html
Copyright © 2020-2023  润新知