LoginSignup
3
0

More than 1 year has passed since last update.

RubyVM::InstructionSequence.load_from_binaryから実行までの流れ

Last updated at Posted at 2022-12-16

はじめに

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でどのように実行されるか
を見てみました。もし間違いなどありましたらコメントで指摘して頂けると
ありがたいです。
ありがとうございました。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0