はじめに
iseqがバイナリーデータからどのように
実行サれるかを見ていきます。
入力データ
puts "hello world"をInstructionSequenceでバイナリーデータにします。
iseq = RubyVM::InstructionSequence.compile(<<~SRC)
puts "Hello world"
SRC
bin = iseq.to_binary.bytes.each_slice(32).map {|a|
a.map{|b| b}.join(',')
}.join(",")
p bin
この結果がこちら
"89,65,82,66,3,0,0,0,2,0,0,0,212,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,148,0,0,0,196,0,0,0,120,
56,54,95,54,52,45,108,105,110,117,120,0,37,43,5,103,121,3,1,3,3,3,1,3,1,1,3,0,255,255,
255,255,255,255,255,255,1,1,3,5,5,0,0,7,41,3,1,1,1,13,73,11,1,1,1,1,1,1,1,1,63,1,3,3,3,
3,7,3,1,3,39,63,23,9,11,1,11,0,255,255,255,255,255,255,255,255,1,0,255,255,255,255,255,
255,255,255,11,3,1,1,1,1,1,1,3,5,1,1,0,0,0,85,0,0,0,241,9,0,0,69,5,21,60,99,111,109,112,
105,108,101,100,62,0,0,0,69,3,23,72,101,108,108,111,32,119,111,114,108,100,0,0,20,5,9,
112,117,116,115,0,152,0,0,0,156,0,0,0,172,0,0,0,188,0,0,0"
このデータを今度はload_from_binaryに入れて実行するコードがこちら
bin_arr = [89,65,82,66,3,0,0,0,2,0,0,0,212,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,148,0,0,0,196,0,0,0,120,56,54,95,54,52,45,108,105,110,117,120,0,37,43,5,103,121,3,1,3,3,3,1,3,1,1,3,0,255,255,255,255,255,255,255,255,1,1,3,5,5,0,0,7,41,3,1,1,1,13,73,11,1,1,1,1,1,1,1,1,63,1,3,3,3,3,7,3,1,3,39,63,23,9,11,1,11,0,255,255,255,255,255,255,255,255,1,0,255,255,255,255,255,255,255,255,11,3,1,1,1,1,1,1,3,5,1,1,0,0,0,85,0,0,0,241,9,0,0,69,5,21,60,99,111,109,112,105,108,101,100,62,0,0,0,69,3,23,72,101,108,108,111,32,119,111,114,108,100,0,0,20,5,9,112,117,116,115,0,152,0,0,0,156,0,0,0,172,0,0,0,188,0,0,0]
bin = bin_arr.pack("c*")
iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
iseq2.eval
これを実行すれば
Hello Worldと表示されます。
ではさっそくload_from_binaryから見ていきましょう。
load_from_binaryからの流れ
次の関数でbinaryデータからiseqのデータが生成されます。
iseqw_s_load_from_binary
rb_iseq_ibf_load
ibf_load_setup
ibf_load_setup_bytes
ibf_load_iseq
rb_ibf_load_iseq_complete
ibf_load_iseq_each
ibf_load_object
ibf_load_code
headerを読み込む
ibf_load_setup_bytesでbufferをheaderにデータを読み込みます。
13180 ibf_load_setup_bytes(struct ibf_load *load, VALUE loader_obj, const char *bytes, size_t size)
13181 {
13182 load->loader_obj = loader_obj;
13183 load->global_buffer.buff = bytes;
13184 load->header = (struct ibf_header *)load->global_buffer.buff;
iseq型のデータを読み込む
ibf_load_iseq_eachでiseqのbodyデータが読み込まれて
実行されるデータがibf_load_codeで読み込まれます。
11409 static VALUE *
11410 ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecode_offset, ibf_offset_t bytecode_size, unsigned int iseq_size)
11411 {
...
11432
11433 for (code_index=0; code_index<iseq_size;) {
11434 /* opcode */
11435 const VALUE insn = code[code_index] = ibf_load_small_value(load, &reading_pos);
11436 const char *types = insn_op_types(insn);
11437 int op_index;
11438
11439 code_index++;
11440
11441 /* operands */
11442 for (op_index=0; types[op_index]; op_index++, code_index++) {
11443 const char operand_type = types[op_index];
11444 switch (operand_type) {
11445 case TS_VALUE:
11446 {
11447 VALUE op = ibf_load_small_value(load, &reading_pos);
11448 VALUE v = ibf_load_object(load, op);
11449 code[code_index] = v;
11450 if (!SPECIAL_CONST_P(v)) {
11451 RB_OBJ_WRITTEN(iseqv, Qundef, v);
11452 ISEQ_MBITS_SET(mark_offset_bits, code_index);
11453 needs_bitmap = true;
11454 }
11455 break;
11456 }
11457
一回目はココに来てstringをloadする関数ポインター(ibf_load_object_string)が呼ばれます。
11515 case TS_CALLDATA:
11516 {
11517 code[code_index] = (VALUE)cd_entries++;
11518 }
11519 break;
...
11536 }
11537 if (insn_len(insn) != op_index+1) {
11538 rb_raise(rb_eRuntimeError, "operand size mismatch");
11539 }
11540 }
11541
二回目はここでrb_call_dataがcodeに入ります。
11542 load_body->iseq_encoded = code;
...
11560 return code;
11561 }
ここでcodeがbodyのiseq_encodedに入れられます。
これがevalでPCポインターに入り実行されていきます。
evalからの流れ
3891 rb_define_method(rb_cISeq, "eval", iseqw_eval, 0);
(iseq.c)
1548 static VALUE
1549 iseqw_eval(VALUE self)
1550 {
1551 return rb_iseq_eval(iseqw_check(self));
1552 }
(iseq.c)
2621 rb_iseq_eval(const rb_iseq_t *iseq)
2622 {
2623 rb_execution_context_t *ec = GET_EC();
2624 VALUE val;
2625 vm_set_top_stack(ec, iseq);
2626 val = vm_exec(ec, true);
2627 return val;
2628 }
(vm.c)
rubyのevalからrb_iseq_evalが呼ばれます。
iseqのencodedがPCポインターへ
661 static void
662 vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq)
663 {
664 if (ISEQ_BODY(iseq)->type != ISEQ_TYPE_TOP) {
665 rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence");
666 }
667
668 /* for return */
669 vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, rb_ec_thread_ptr(ec)->top_self,
670 VM_BLOCK_HANDLER_NONE,
671 (VALUE)vm_cref_new_toplevel(ec), /* cref or me */
672 ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp,
673 ISEQ_BODY(iseq)->local_table_size, ISEQ_BODY(iseq)->stack_max);
674 }
(vm.c)
vm_set_top_stackでiseqのbodyのiseq_encodedがpcポインターにセットされます
iseqの配列をループする
70 static VALUE
71 vm_exec_core(rb_execution_context_t *ec, VALUE initial)
72 {
73
...
142
143 first:
144 INSN_DISPATCH();
145 /*****************/
146 #include "vm.inc"
147 /*****************/
148 END_INSNS_DISPATCH();
149
150 /* unreachable */
151 rb_bug("vm_eval: unreachable");
152 goto first;
153 }
(vm_exec.c)
そのpcポインターがvm.incの中の
INSN_DISPATCH();でswitch文になり
146 #define INSN_DISPATCH() \
147 while (1) { \
148 switch (GET_CURRENT_INSN()) {
...
150 #define END_INSNS_DISPATCH() \
151 default: \
152 SDR(); \
153 rb_bug("unknown insn: %ld", GET_CURRENT_INSN()); \
154 } /* end of switch */ \
155 } /* end of while loop */ \
156
(vm_exec.h)
それぞれのINSN_ENTRYの中でADD_PCを読んでpcポインターを進めながら
ループして行きます。
stringをSPポインターへ
/* insn putstring(str)()(val) */
INSN_ENTRY(putstring)
{
/* ### Declare that we have just entered into an instruction. ### */
START_OF_ORIGINAL_INSN(putstring);
DEBUG_ENTER_INSN("putstring");
/* ### Declare and assign variables. ### */
VALUE str = (VALUE)GET_OPERAND(1);
# define INSN_ATTR(x) attr_ ## x ## _putstring(str)
const bool leaf = INSN_ATTR(leaf);
VALUE val;
/* ### Instruction preambles. ### */
if (! leaf) ADD_PC(INSN_ATTR(width));
SETUP_CANARY(leaf);
COLLECT_USAGE_INSN(INSN_ATTR(bin));
COLLECT_USAGE_OPERAND(INSN_ATTR(bin), 0, str);
/* ### Here we do the instruction body. ### */
# define NAME_OF_CURRENT_INSN putstring
# line 390 "insns.def"
{
val = rb_ec_str_resurrect(ec, str);
}
# line 994 "vm.inc"
# undef NAME_OF_CURRENT_INSN
/* ### Instruction trailers. ### */
CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, INSN_ATTR(retn));
CHECK_CANARY(leaf, INSN_ATTR(bin));
INC_SP(INSN_ATTR(sp_inc));
TOPN(0) = val;
VM_ASSERT(!RB_TYPE_P(TOPN(0), T_NONE));
VM_ASSERT(!RB_TYPE_P(TOPN(0), T_MOVED));
if (leaf) ADD_PC(INSN_ATTR(width));
# undef INSN_ATTR
/* ### Leave the instruction. ### */
END_INSN(putstring);
}
(vm.inc)
rb_ec_str_resurrectでstringを取り
TOPN(0) = valでそのstringがspポインターに入れられます。
putsを呼ぶ
/* insn opt_send_without_block(cd)(...)(val) */
INSN_ENTRY(opt_send_without_block)
{
/* ### Declare that we have just entered into an instruction. ### */
START_OF_ORIGINAL_INSN(opt_send_without_block);
DEBUG_ENTER_INSN("opt_send_without_block");
/* ### Declare and assign variables. ### */
CALL_DATA cd = (CALL_DATA)GET_OPERAND(1);
# define INSN_ATTR(x) attr_ ## x ## _opt_send_without_block(cd)
const bool leaf = INSN_ATTR(leaf);
VALUE val;
/* ### Instruction preambles. ### */
if (! leaf) ADD_PC(INSN_ATTR(width));
POPN(INSN_ATTR(popn));
SETUP_CANARY(leaf);
COLLECT_USAGE_INSN(INSN_ATTR(bin));
COLLECT_USAGE_OPERAND(INSN_ATTR(bin), 0, cd);
/* ### Here we do the instruction body. ### */
# define NAME_OF_CURRENT_INSN opt_send_without_block
# line 818 "insns.def"
{
VALUE bh = VM_BLOCK_HANDLER_NONE;
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
if (val == Qundef) {
RESTORE_REGS();
NEXT_INSN();
}
}
# line 2284 "vm.inc"
# undef NAME_OF_CURRENT_INSN
/* ### Instruction trailers. ### */
CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, INSN_ATTR(retn));
CHECK_CANARY(leaf, INSN_ATTR(bin));
PUSH(val);
if (leaf) ADD_PC(INSN_ATTR(width));
# undef INSN_ATTR
/* ### Leave the instruction. ### */
END_INSN(opt_send_without_block);
}
(vm.inc)
opt_send_without_block内で
POPN(INSN_ATTR(popn)) でSPを減らしてstring
を引数にしてvm_sendishを呼びrb_call_data
に設定された関数ポインターを呼びます。
これでrb_io_putsが呼ばれます。
おわりに
ものすごくざっとload_from_binaryからevalでどのように実行されるか
を見てみました。もし間違いなどありましたらコメントで指摘して頂けると
ありがたいです。
ありがとうございました。