=begin

    动态生成机器语言,搭建机器语言 -> ruby解释器 -> ruby代码之间的桥梁
        by sxysxy 2016.11.28
=end

require 'fiddle'
require './caller_ext.so'
include Fiddle

class OpCode
    attr_accessor :ptr
    attr_accessor :length
    #gen_code
    #用法: obj,持有方法method的对象
    #      method, 方法名(一个字符串)
    #      argc, 被回调的ruby "函数" 需要的参数的个数
    #      proto, 调用协议,默认cdecl
    # stdcall的实现没写(留做作业噗)...如果需要支持stdcall,需要最后再把调用者的压栈平衡掉。(也就多两行代码..)
    def gen_code(obj, method, argc, proto = :cdecl)  
        s = ""
        s += [0x55].pack("C") #push ebp
        s += [0x89, 0xe5].pack("CC") #mov ebp, esp
      
        cnt = 4+argc*4;   #参数地址偏移量
        argc.times do
            s += [0x8b, 0x45].pack("CC")+[cnt].pack("C") #mov eax, [ebp+cnt]
            s += [0xd1, 0xe0].pack("CC") #shl eax, 1
            s += [0x40, 0x50].pack("CC") #inc eax, push eax
            cnt -= 4
        end
       
        s += ([0x68]+[argc].pack("L").bytes).pack("C*") #push dword argc
        s += ([0x68]+[get_intern(method)].pack("L").bytes).pack("C*") #push dword method
        s += ([0x68]+[obj.get_ptr_val].pack("L").bytes).pack("C*") #push dword obj
        
        #call rb_funcall
        #s += [0x9a].pack("C")+[get_rb_funcall].pack("L")+[0].pack("S")
        s += [0xb9].pack("C")+[get_rb_funcall].pack("L") #mov ecx, rb_funcall
        s += [0xff, 0xd1].pack("CC")    #call ecx

        s += [0x89, 0xc3].pack("CC")   #mov ebx, eax
        s += ([0xb8]+[argc].pack("L").bytes).pack("C*") #mov eax, argc
        s += [0x83, 0xc0, 0x03].pack("CCC")  #add eax, byte 3
        s += [0xc1, 0xe0, 0x02].pack("CCC")  #shl eax, byte 2
        s += [0x01, 0xc4].pack("CC")    #add esp, eax
        s += [0x89, 0xd8].pack("CC")    #mov eax, ebx
        s += [0x5d, 0xc3].pack("CC")    #pop ebp, ret
        
        [url=home.php?mod=space&uid=2661269]@PTR[/url] = Fiddle::Pointer.malloc(s.length)
        @ptr[0, s.length] = s
        @length = s.length
        self
    end
    def free
        @ptr.free
    end
    def addr
        @ptr.to_i
    end
end

def test_call(x)  #被测试调用的函数
    puts "Called! arg x = #{x}"
end

c = OpCode.new
c.gen_code(Kernel, "test_call", 1)
File.open("test_gen_code.bin", "wb") do |f|
    f.write c.ptr[0, c.length]  #这里把生成的机器语言输出到文件,方便反汇编查看
end

addr = dlopen("caller_test.dll")['caller']
caller = Function.new(addr, [TYPE_LONG], TYPE_VOID)    
caller.call c.addr  #把c.addr作为函数指针传入。
c.free     #释放机器语言占用的内存