在Windows上使用Wilson

之前被NS老兄激起了兴趣发过[url=http://rednaxelafx.iteye.com/blog/431121]beef[/url]帖,主要是显示可以很容易的写出Ruby扩展用于直接生成机器码,连接到Ruby的对象系统上,然后像调用普通Ruby方法一样去使用。我觉得这个很有趣,想看看有没有好的办法做个internal DSL出来在Ruby里写类似MASM语法的汇编,然后让Bk201生成机器码出来执行。

昨天看到某帖之后又把我的兴趣激起来了……在动手实现DSL之前,我先搜了一下Ruby assembler,发现Ryan Davis写了个叫[url=http://blog.zenspider.com/rubysadism/wilson/]Wilson[/url]的库,功能跟我想要的很类似,虽然语法有点不同,好歹还是NASM而不是AT&T式的。当前版本是1.1.1。我现在是在Windows XP SP3/Ruby 1.8.6上测试的。

安装该库用:
加载该库用require 'wilson'。它会对Ruby一些核心类做monkey patch,要注意。

原本Wilson是为在Mac上使用而编写,但整个代码都是纯Ruby的,所以在Windows上使用稍微修改一下平台相关的代码就行。在ruby安装目录的gems/1.8/gems/wilson-1.1.1/lib/目录下,
wilson.rb,第1135-1136行:
[code="ruby"> dir = File.join(Config::CONFIG["prefix"], "lib")
dlload File.join(dir, "libruby.dylib")

.dylib是Mac上的动态链接库文件的后缀。这里要改为:
  dir = File.join(Config::CONFIG["prefix"], "bin")
dlload File.join(dir, Config::CONFIG["LIBRUBY_SO"])

就可以在Windows上正常使用了。

Wilson在一些测试里会使用到RubyInline,有兴趣的话也可以装上:
加载该库用require 'inline'。


如果把MASM语法下op dest, src的形式看作前缀表达式,那么Wilson的DSL表现出来的就是dest.op src形式的“中缀表达式”。所以原本写作xor eax, eax的汇编,在Wilson就写作eax.xor eax。习惯了之后这么写也挺顺的。
让我们来看看Wilson in action~
[code="ruby">require 'rubygems'
require 'wilson'

class A
defasm :add, :a, :b do
a, b = arg(0), arg(1)
eax.mov a
eax.add b
eax.dec
end

defasm :ary_len, :ary do
ary = arg(0)
eax.mov ary
eax.add 4*2 # skip RBasic.flags and RBasic.klass
eax.mov eax.m
to_ruby eax
end

defasm :ary_store, :ary, :idx, :val do
ary, idx, val = arg(0), arg(1), arg(2)
eax.mov ary
ecx.mov(eax + 4*4) # address of RArray.ptr
edx.mov idx
from_ruby edx
edx.shl 2
ecx.add edx
edx.mov val
ecx.m.mov edx
end
end

a = A.new
p a.add(3, 5) #=> 8
p a.ary_len([]) #=> 0
p a.ary_len([2, 4, 6, 8]) #=> 4
ary = [1, 2, 3]
a.ary_store(ary, 2, 4) #=> [1, 2, 4]
p ary #=> [1, 2, 4]

Wilson的实现暂时还不太完善,例如说声明一个汇编方法用defasm,其参数包括生成方法的名字和参数列表(的名字),但在定义方法体时却暂时还无法用名字与引用参数,只能通过arg(0)、arg(1)之类的形式去引用。寻址模式中放大倍数的版本也都还没实现,上面代码中的ary_store本来可以用lea ecx, dword ptr [ecx + edx*4]一条指令完成一个乘法和一个加法,但Wilson还不支持edx*4的形式,非要用的话只好如下所示:
class A
defasm :ary_store, :ary, :idx, :val do
ary, idx, val = arg(0), arg(1), arg(2)
eax.mov ary
ecx.mov(eax + 4*4) # RArray.ptr
edx.mov idx
from_ruby edx
# ecx.lea(ecx + edx*4) # this is not supported by Wilson yet
# hack: lea ecx, dword ptr [ecx + edx*4]
self.stream.concat [0x8D, 0x0C, 0x91]
edx.mov val
ecx.m.mov edx
end
end

把机器码硬插到stream里……OTL

另外Wilson在把某些指令汇编到机器码时有问题。我试过跟ebx相关的几条指令,生成出来的都是错的:(测试摘自Wilson里的test_wilson.rb,注释是我添加的)
  def test_mov_ebx_m_ecx_edx_offset
# mov ebx, dword ptr [ecx + edx + 1]
# 8B 5C 11 01
asm.ebx.mov(asm.ecx + asm.edx + 1)
# this test looks broken
# 8B 1C 51 01 # wrong machine code
assert_equal [0x8B, 0b00011100, 0b01010001, 1], stream
end

def test_mov_ebx_m_ecx_edx_big_offset
# mov ebx, dword ptr [ecx + edx + 256]
# 8B 9C 11 00 01 00 00
asm.ebx.mov(asm.ecx + asm.edx + 256)
# this test looks broken as well...
# 8B 1C 91 00 01 00 00 # wrong machine code
assert_equal [0x8B, 0b00011100, 0b10010001, 0, 1, 0, 0], stream
end

def test_mov_m_ecx_edx_offset_ebx
# mov dword ptr [ecx + edx + 1], ebx
# 89 5C 11 01
(asm.ecx + asm.edx + 1).mov asm.ebx
# this test looks broken
# 89 1C 51 01 # is everything related to ebx broken?
assert_equal [0x89, 0b00011100, 0b01010001, 1], stream
end

为什么作者的测试用例里assert的数据就已经跟我所知道的机器码不一致了 OTL
回头我把它修修看看有没有必要发个patch过去……人家或许对Windows没兴趣,顺带就对这patch没兴趣了 =v=

顺带说说上面的例子是如何能运行的。注意到我什么错误检查都没做,所以传入的参数跟我预期的使用方式不符的话程序基本上就要crash了。

第一个例子比较直观,只有最后的dec eax指令可能有点怪。这是因为Ruby的Fixnum表现的是31位带符号整数,普通的31位补码表示的整数要转换为Fixnum的公式是(n << 1) + 1。例子中a与b都是Fixnum,它们的高31位就是原本的数值,而末尾的1则是Fixnum的tag。把它们直接相加,虽然高位是对位加起来了,但末尾的tag本来不该参与运算却也相加了,于是通过dec eax指令把多加的tag再减回去。

后两个例子都涉及到CRuby中对象布局的实现细节。CRuby 1.8.6里有这两个结构体:
struct RBasic {
unsigned long flags;
VALUE klass;
};

struct RArray {
struct RBasic basic;
long len;
union {
long capa;
VALUE shared;
} aux;
VALUE *ptr;
};

数组对象的背后就是靠RArray结构体来支撑的,而它的开头又包含了一个RBasic结构体。可以把RBasic看作是所有Ruby对象都有的header。由此可以知道,在32位x86上,RArray的len成员位于偏移量4*2 == 8的位置上,表示该数组对象的长度(元素个数);ptr成员位于偏移量4*4 == 16的位置上,它所指向的就是数组的实际内容,是一大块连续的空间。

在第二个例子,ary_len里,首先把第一个参数放入eax中。这个参数是指向一个RArray实例的指针。然后再把eax += 8,得到RArray.len的地址。接着通过mov eax, dword ptr [eax]指令,把RArray.len的值取出来。最后使用to_ruby“宏”将数字转换为Ruby的Fixnum,返回。

在第三个例子,ary_store里,过程跟前一个例子差不多,关键思路是达到RARRAY(ary)->ptr[idx] = val的目的。

OK,可以靠Wilson做点更有趣的事情了 ^ ^
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值