从ruby到x86机器语言
来源:互联网 发布:i代表什么矩阵 编辑:程序博客网 时间:2024/06/07 11:07
(同步个人博客 http://sxysxy.org/blogs/69 到csdn
在最开始之前,我要特别感谢 兰兰姐姐 在相关技术上给予我的指导。也正是在兰兰姐姐的帮助下,我才能较为顺利地进行 XYGui 的开发。
本篇文章主要要解决的问题是,ruby写的”函数”作为”函数指针”被c/c++等语言(其实是机器语言)调用
准备
在这里我假定泥大概看过ruby的源码,知道怎么写ruby的c扩展,略懂一些汇编语言与操作系统相关的原理。请安装devkit for ruby(on windows)
随便写个小东西
写个这样的dll caller_test.c -> caller_test.dll :
#include <windows.h>#define DLL_API __declspec(dllexport) DLL_API void caller(void (*f) (int x)){ f(2);}DLL_API BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD uReason, DWORD lpReversed){ switch(uReason) { case DLL_PROCESS_ATTACH: puts("Dll Attached"); break; case DLL_PROCESS_DETACH: puts("Dll Detached"); break; } return TRUE;}
函数caller接收一个函数指针f,然后用2调用它。这个f函数我们用ruby来写:
在dll同目录下先写一个caller_test1.rb:
require 'fiddle'include Fiddleproc = Class.new(Closure) do define_method :call do |x| puts "Called with x:#{x}" x endend.new(TYPE_INT, [TYPE_INT])fptr = Function.new(proc, [TYPE_INT], TYPE_INT)addr = dlopen("caller_test.dll")['caller']caller = Function.new(addr, [TYPE_LONG], TYPE_VOID) caller.call fptr.to_i
执行这个脚本,即可得到输出”Called with x:2”。脚本内借助fiddle,让dll成功回调了ruby写的”函数”。
现在我们要做的就是不借助Fiddle::Closure,实现dll调用ruby。
ruby解释器调用ruby函数的途径
ruby解释器内有一个叫做rb_funcall的函数,这是ruby解释器调用ruby的方法的接口。第一个参数是对象,也就是”self”,第二个参数是方法名,第三个参数是调用这个ruby方法的参数个数,后面跟着参数列表。也就是rb_funcall(obj, rb_intern(“method”), argc, …)。所以让机器语言调用ruby的”函数”的方法也就显而易见了: 让ruby动态生成一段机器语言,调用rb_funcall并把所需要的参数压入栈即可。下面就来实现这个。
首先要做一个ruby的c扩展,用以得到rb_funcall的地址,和rb_intern(“call”)的值,并封装一些必要的函数。现在来写一个 caller_ext.c (如果泥是直接复制粘贴的代码,可要注意文件名一定是caller_ext.c),(不要问我怎么编译c扩展):
#include "ruby.h"VALUE get_rb_funcall(VALUE self){ return INT2NUM((int)rb_funcall);}VALUE get_ptr_val(VALUE self){ return INT2NUM(self);}VALUE get_intern(VALUE self, VALUE name){ return INT2NUM(rb_intern(RSTRING_PTR(name)));}void Init_caller_ext(){ rb_define_method(rb_mKernel, "get_rb_funcall", get_rb_funcall, 0); rb_define_method(rb_mKernel, "get_ptr_val", get_ptr_val, 0); rb_define_method(rb_mKernel, "get_intern", get_intern, 1);}
get_ptr_val用于得到ruby对象的指针的值,get_rb_funcall用于得到rb_funcall的地址,get_intern用于得到一个方法名的”ruby内部表示法”(算是个哈希值)。
做个试验
require './caller_ext'require 'fiddle'include Fiddlef = Function.new(get_rb_funcall, [TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT], TYPE_INT)f.call get_ptr_val, get_intern("puts"), 1, "233".get_ptr_val #=> 233
正确地输出了233
然后我们用ruby动态地生成机器语言,调用rb_funcall,实现这样的过程,先贴出来代码
=begin 动态生成机器语言,搭建机器语言 -> ruby解释器 -> ruby代码之间的桥梁 by sxysxy 2016.11.28=endrequire 'fiddle'require './caller_ext.so'include Fiddleclass 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 @ptr = 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 endenddef test_call(x) #被测试调用的函数 puts "Called! arg x = #{x}"endc = OpCode.newc.gen_code(Kernel, "test_call", 1)File.open("test_gen_code.bin", "wb") do |f| f.write c.ptr[0, c.length] #这里把生成的机器语言输出到文件,方便反汇编查看endaddr = dlopen("caller_test.dll")['caller']caller = Function.new(addr, [TYPE_LONG], TYPE_VOID) caller.call c.addr #把c.addr作为函数指针传入。c.free #释放机器语言占用的内存
nasm真是个好东西,我写了一份汇编语言版”调用桥梁”,直接能编译成纯二进制文件,然后反汇编一下对着填数就好了/w\。
测试输出依然正确: Called! arg x = 2 。而且解释器正常运行,没有崩掉哦。
反汇编一下我们再这个例子中生成的二进制代码:
00000000 55 push ebp00000001 89E5 mov ebp,esp00000003 8B4508 mov eax,[ebp+0x8]00000006 D1E0 shl eax,100000008 40 inc eax00000009 50 push eax0000000A 6801000000 push dword 0x10000000F 68D0510000 push dword 0x51d000000014 68E8797102 push dword 0x27179e800000019 B97016A46D mov ecx,0x6da416700000001E FFD1 call ecx00000020 89C3 mov ebx,eax00000022 B801000000 mov eax,0x100000027 83C003 add eax,byte +0x30000002A C1E002 shl eax,byte 0x20000002D 01C4 add esp,eax0000002F 89D8 mov eax,ebx00000031 5D pop ebp00000032 C3 ret
就是倒着压入调用者给的参数,然后倒着压入argc, method, obj(遵循cdecl调用协议), 调用rb_funcall,完事后平衡下栈,返回。不过用机器语言还真是…调试了不短时间呢。
好了,这个坑也算是填完啦!
- 从ruby到x86机器语言
- 从机器语言到汇编语言
- 从 Java 到 Ruby
- 从Ruby 到Python
- 从php到Ruby
- 机器语言社会到汇编社会
- 机器语言
- 机器语言
- 机器语言
- 机器语言
- 从10.04 x86 到 11.04 AMD64 + win7
- 《Ruby从入门到精通》 2.10 Ruby历史和社区
- 把ruby源从taobao更新到ruby-china
- Ruby 从入门到精通 译者序
- 《Ruby从入门到精通》部署Ruby应用和程序库 Ruby高级功能 综合演练
- ruby&python 从CGI 到 WSGI 到 Rack, 顺带Sinatra
- qt从x86架构移植到arm架构
- 操作系统实战之从裸机到内核(x86-64)
- Java字节码浅析(—)
- Java wait() notify()方法使用实例讲解
- POJ 3107 求树的重心
- TMS320F28335/ <1> 开发环境的搭建
- 进程之间的通信的方式有哪些,他们之间的区别是什么!或者是忧缺点
- 从ruby到x86机器语言
- Linux搜索指定文件夹并打开最符合搜索目标名称的文件
- h5-css3新增背景属性
- android开发 MVP模式介绍与实战
- servlet概念 作用 流程
- 闰年判断
- 实例分析Java Class的文件结构
- eclipse导入新工程
- 随机梯度下降(Stochastic gradient descent)和 批量梯度下降(Batch gradient descent )的公式对比、实现对比