标题: VB6拾遗:内联汇编
作者: Demon
链接: https://demon.tw/programming/vb6-repick-inline-assembly.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
虽然VB中没有原生的内联汇编支持,但是通过轻量级COM对象可以间接实现。
严格的说这不是真正的内联汇编,因为你不能在任何地方使用,只能在轻量级对象的虚表(VTable)中使用,而且与其说是内联汇编,倒不如说是内联机器码。
为什么要使用内联汇编?比较“低级”的C语言编译器一般都支持内联汇编功能,可除了一些底层的系统,又有多少程序要用到内联汇编?更何况比较“高级”的VB?
C程序用不上内联汇编,是因为C语言提供了足够强大的功能,或者说C语言本身已经比较底层了。而VB隐藏了太多底层的东西,所以有些功能必须通过汇编来实现。别的先不说,就拿移位运算来说,除了VB以外,你见过哪钟语言没有移位运算符的?虽然可以用乘除法来模拟,但是要充分考虑各种溢出并且效率低下,我们今天就用内联汇编来实现移位运算。
首先用.idl文件定义好接口,用midl.exe编译成.tlb文件并添加引用:
[ uuid(6BEE1180-1F4A-47D9-B192-5FF90DDF8827), version(1.0) ] library BitLib { importlib("stdole2.tlb"); [ odl, uuid(E690B781-B712-4E59-A643-AA4AE9EE78BE), nonextensible ] interface IBit : IUnknown { long _stdcall LeftShift( [in] long a, [in] long b); long _stdcall RightShift( [in] long a, [in] long b); }; };
然后添加一个标准模块:
Option Explicit 'VB6 bit shift lightweight object 'By Demon 'https://demon.tw Private Declare Function CoTaskMemAlloc Lib "ole32.dll" (ByVal cb As Long) As Long Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long) Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ Destination As Any, Source As Any, ByVal Length As Long) Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" ( _ dest As Any, ByVal numBytes As Long) Private Type BitVTable VTable(4) As Long End Type Private Type Bit pVTable As Long cRefs As Long End Type Private Type LeftShift ASM(3) As Long End Type Private Type RightShift ASM(3) As Long End Type Private m_VTable As BitVTable Private m_pVTable As Long Private m_LeftShift As LeftShift Private m_RightShift As RightShift Public Function CreateBit() As IUnknown Dim Struct As Bit Dim ThisPtr As Long If m_pVTable = 0 Then With m_VTable .VTable(0) = FuncAddr(AddressOf QueryInterface) .VTable(1) = FuncAddr(AddressOf AddRef) .VTable(2) = FuncAddr(AddressOf Release) With m_LeftShift .ASM(0) = &H824448B .ASM(1) = &HC244C8B .ASM(2) = &HCC2E0D3 .ASM(3) = &HCCCCCC00 '8B4424 08 mov eax, dword ptr [esp+8] '8B4C24 0C mov ecx, dword ptr [esp+0C] 'D3E0 shl eax, cl 'C2 0C00 retn 0C 'CC int3 'CC int3 'CC int3 End With .VTable(3) = VarPtr(m_LeftShift) With m_RightShift .ASM(0) = &H824448B .ASM(1) = &HC244C8B .ASM(2) = &HCC2E8D3 .ASM(3) = &HCCCCCC00 '8B4424 08 mov eax, dword ptr [esp+8] '8B4C24 0C mov ecx, dword ptr [esp+0C] 'D3E8 shr eax, cl 'C2 0C00 retn 0C 'CC int3 'CC int3 'CC int3 End With .VTable(4) = VarPtr(m_RightShift) m_pVTable = VarPtr(.VTable(0)) End With End If ThisPtr = CoTaskMemAlloc(LenB(Struct)) If ThisPtr = 0 Then Err.Raise 7 With Struct .pVTable = m_pVTable .cRefs = 1 End With CopyMemory ByVal ThisPtr, Struct.pVTable, LenB(Struct) ZeroMemory Struct.pVTable, LenB(Struct) CopyMemory CreateBit, ThisPtr, 4 End Function Private Function QueryInterface(This As Bit, riid As Long, pvObj As Long) As Long With This pvObj = VarPtr(.pVTable) .cRefs = .cRefs + 1 End With End Function Private Function AddRef(This As Bit) As Long With This .cRefs = .cRefs + 1 AddRef = .cRefs End With End Function Private Function Release(This As Bit) As Long With This .cRefs = .cRefs - 1 Release = .cRefs If .cRefs = 0 Then DeleteThis This End If End With End Function Private Function FuncAddr(ByVal pfn As Long) As Long FuncAddr = pfn End Function Private Sub DeleteThis(This As Bit) Dim tmp As Bit Dim pThis As Long pThis = VarPtr(This) CopyMemory ByVal VarPtr(tmp), ByVal pThis, LenB(This) CoTaskMemFree pThis End Sub
再添加一个标准模块:
'By Demon 'https://demon.tw Sub Main() Dim IBit As IBit Set IBit = Bit.CreateBit Debug.Print Hex$(IBit.LeftShift(1, 31)) Debug.Print Hex$(IBit.RightShift(&H80000000, 1)) End Sub
运行一下看看效果,输出80000000和40000000,还不错。
跟普通的轻量级对象一样,Bit对象VTable的前三个指针都是QueryInterface、AddRef和Release;而后两个指针却指向一个结构的地址,而该结构的内容是一串神奇的数字:
With m_LeftShift .ASM(0) = &H824448B .ASM(1) = &HC244C8B .ASM(2) = &HCC2E0D3 .ASM(3) = &HCCCCCC00 '8B4424 08 mov eax, dword ptr [esp+8] '8B4C24 0C mov ecx, dword ptr [esp+0C] 'D3E0 shl eax, cl 'C2 0C00 retn 0C 'CC int3 'CC int3 'CC int3 End With .VTable(3) = VarPtr(m_LeftShift) With m_RightShift .ASM(0) = &H824448B .ASM(1) = &HC244C8B .ASM(2) = &HCC2E8D3 .ASM(3) = &HCCCCCC00 '8B4424 08 mov eax, dword ptr [esp+8] '8B4C24 0C mov ecx, dword ptr [esp+0C] 'D3E8 shr eax, cl 'C2 0C00 retn 0C 'CC int3 'CC int3 'CC int3 End With .VTable(4) = VarPtr(m_RightShift)
很显然,这些神奇的数字就是机器码,下面注释是对应的汇编代码。将汇编转成机器码的方法很多,可以用NASM、MASM、Visual C++等。
简单介绍一下用Visual C++将汇编代码转成相应机器码的方法:
int main() { _asm { mov eax, dword ptr [esp+8] mov ecx, dword ptr [esp+0Ch] shl eax, cl retn 0Ch } return 0; }
调试上面的代码,在汇编窗口中就能看到对应的机器码,不过复制到VB中时要注意字节的顺序并用CC(int 3指令,当然也可以用nop指令)补齐:
23: _asm 24: { 25: mov eax, dword ptr [esp+8] 00401028 8B 44 24 08 mov eax,dword ptr [esp+8] 26: mov ecx, dword ptr [esp+0Ch] 0040102C 8B 4C 24 0C mov ecx,dword ptr [esp+0Ch] 27: shl eax, cl 00401030 D3 E0 shl eax,cl 28: retn 0Ch 00401032 C2 0C 00 ret 0Ch 29: }
为什么VB会执行我们构造出来的机器码呢?接口中定义的方法对应着对象VTable的指针,VTable(3)指向m_LeftShift结构的地址,当调用LeftShift方法时,VB编译器生成的是一条call指令,所以该地址的内容,也就是我们构造的机器码会被执行。
赞赏微信赞赏支付宝赞赏
随机文章:
demon,你快成神了,VB都能玩的这么变态、
看完你写的,我就觉得我完全没学过