专栏首页智能大石头C#嵌入x86汇编——一个GPIO接口的实现

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.png

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

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:http://nnhy.cnblogs.com/复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 说好的读源码变成了学习教程(无刷银剑固件)

    本来是想写个源码分析的,结果分析了半道发现和我想的不一样,就当一个STM32的学习指南了。

    云深无际
  • 基于FPGA的轻量级RISCV SoC

    随着以RISC-V(RISC,精简指令集计算机;V表示为第五代)为代表的新型开源ISA(instruction set architecture,指令集架构)的...

    FPGA技术江湖
  • 迅为2K1000龙芯开发板pmon 下操作 GPIO

    我们可以来学习如何在 pmon 下操作 gpio 了, 为什么要把这个需求单独拿出来讲呢? 因为有的时候我们做了一款产品, 在特定的环境下需要让 GPIO 在上...

    用户9167207
  • STM32库开发实战指南 PDF+源码

    还是哪个永恒的话题的,学习!(今天回家已经很晚了,本来不打算写东西的,不过一直读野火的书,赶紧很好,这里就分享一下)

    云深无际
  • 11_GPIO中断

    ​ 假设你现在正在写作业,突然电话响起,你需要停下写作业接电话,挂电话后继续写作业。突然由人按门铃,你需要先去开门,然后继续回来写作业。电话和门铃打断了写作业,...

    韦东山
  • Linux——Linux驱动之基本理论常识总结(什么是Linux驱动?Linux驱动需要掌握哪些?)

    前面Linux专题中关于Linux下系统编程总结了17篇博文,主要是为了提高Linux下的C编程应用能力,熟悉Linux编程应用环境,从此篇博文起开始Linux...

    Winter_world
  • C语言嵌入式系统编程修炼之背景篇

    这是我13年前创作和发表在互联网上的文章,这么多年过去了,这篇文章仍然在到处传播。现在贴回Linuxer公众号。 全文目录: C语言嵌入式系统编程修炼之道——背...

    企鹅号小编
  • Linux驱动实践:如何编写【 GPIO 】设备的驱动程序?

    在前几篇文章中,我们一块讨论了:在 Linux 系统中,编写字符设备驱动程序的基本框架,主要是从代码流程和 API 函数这两方面触发。

    IOT物联网小镇
  • 树莓派4裸机基础教程:从hello world开始

    当我们去研究一个系统的时候,首先需要从最简单的程序开始入手。前面文章的介绍已经描述了项目的环境搭建以及启动过程。

    bigmagic
  • VS中使用X64汇编

    需要注意的是,在X86项目中,可以使用__asm{}来嵌入汇编代码,但是在X64项目中,再也不能使用__asm{}来编写嵌入式汇编程序了,必须使用专门的.asm...

    战神伽罗
  • ESP32-ttgo T-Display之开发环境搭建及GPIO操作

      最近想玩玩ESP32,在某宝上买了个ESP32的板子,40块的价格,带有1.14寸TFT显示屏,WiFi和蓝牙,小飞哥觉得还是可以的。入手,盘他。

    用户8913398
  • RISCV 汇编语言程序设计 (1) 跑马灯设计

    本文主要介绍汇编语言程序设计中跑马灯程序的设计要求,GPIO的概念和相关硬件知识,为之后分析汇编程序做准备。

    IC知识库
  • Linux驱动实践:一起来梳理【中断】的前世今生(附代码)

    大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【Linux 中断的注册和处理】。

    IOT物联网小镇
  • 单片机使用汇编开发的简单介绍

    汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其它可编程器件的低级语言。

    韦东山
  • 黑客最简单的软件破解方法,反汇编nop指令覆盖

    群里有个小伙伴学习设计加密方法,如同某商用软件输入注册码后就能使用扩展功能。设计时他很自然的想着所写的加密措施是否足够健壮安全,是否有什么方法可以绕过加密检查,...

    Linux阅码场
  • 物联网时代的嵌入式开发平台

    作为典型的嵌入式开发,物联网应用的开发与互联网应用从硬件配置到运行环境有巨大的不同。本文介绍了当前物联网开发者面临的挑战,并分析IoT时代完整的开发平台至少需要...

    CSDN技术头条
  • 汇编学习总结一(10.27)

    用户4645519
  • 【Linux笔记】LED驱动

    上一篇我们分享了字符设备驱动框架:嵌入式Linux驱动基础,当时分享的是hello驱动程序。学STM32我们从点灯开始,学Linux驱动我们自然也要点个灯来玩玩...

    正念君

扫码关注云+社区

领取腾讯云代金券