Rubyで現在のスタックを取得

こんにちは、Ruby界のパランパンことkubo39です！

Rubyistならときどき現在のスタックを取得したくなることがあるかと思います。

というわけでそんな感じのことできないかな～ってやったらこんな感じでできそうってのを見つけたのでメモ残しときます。

## コード

```c:stackswap.h
#ifndef STACKSWAP_H
#define STACKSWAP_H
#include "ruby/ruby.h"

#ifdef DEVELOP
#define DEBUG(...)						\
  do {								\
    printf("%-22s %-32s%4u: ", __FILE__, __func__, __LINE__);	\
    printf(__VA_ARGS__);					\
    printf("\n");						\
  } while(0)
#define RDEBUG(...)							\
  do {									\
    printf("\x1B[31m%-22s %-32s%4u: ", __FILE__, __func__, __LINE__);	\
    printf(__VA_ARGS__);						\
    printf("\x1B[0m\n");						\
  } while(0)
#else
#define DEBUG(...) do{}while(0)
#define RDEBUG(...) do{}while(0)
#endif


#if __GNUC__ >= 3
# define likely(x)	__builtin_expect(!!(x), 1)
# define unlikely(x)	__builtin_expect(!!(x), 0)
#else
# define likely(x) (x)
# define unlikely(x) (x)
#endif

typedef struct rb_thread_struct {
  VALUE self;
  VALUE vm;

  /* execution information */
  VALUE *stack;		/* must free, must mark */
  size_t stack_size;          /* size in word (byte size / sizeof(VALUE)) */
  VALUE cfp;
  int safe_level;
  int raised_flag;
  VALUE last_status; /* $? */

  /* passing state */
  int state;

  int waiting_fd;

  /* for rb_iterate */
  VALUE passed_block;

  /* for bmethod */
  VALUE passed_me;

  /* for cfunc */
  VALUE passed_ci;

  /* for load(true) */
  VALUE top_self;
  VALUE top_wrapper;

  /* eval env */
  VALUE base_block;

  VALUE *root_lep;
  VALUE root_svar;

  /* thread control */
  VALUE thread_id;
  VALUE status;
  int to_kill;
  int priority;

  VALUE native_thread_data;
  void *blocking_region_buffer;

  VALUE thgroup;
  VALUE value;

  /* temporary place of errinfo */
  VALUE errinfo;

  /* temporary place of retval on OPT_CALL_THREADED_CODE */
#if OPT_CALL_THREADED_CODE
  VALUE retval;
#endif

  /* async errinfo queue */
  VALUE pending_interrupt_queue;
  int pending_interrupt_queue_checked;
  VALUE pending_interrupt_mask_stack;

  VALUE interrupt_flag;
  unsigned long interrupt_mask;
  VALUE interrupt_lock;
  VALUE interrupt_cond;
  VALUE unblock;
  VALUE locking_mutex;
  VALUE  keeping_mutexes;

  VALUE tag;
  VALUE protect_tag;

  /*! Thread-local state of evaluation context.
   *
   *  If negative, this thread is evaluating the main program.
   *  If positive, this thread is evaluating a program under Kernel::eval
   *  family.
   */
  int parse_in_eval;

  /*! Thread-local state of compiling context.
   *
   * If non-zero, the parser does not automatically print error messages to
   * stderr. */
  int mild_compile_error;

  /* storage */
  VALUE local_storage;

  VALUE join_list;

  VALUE first_proc;
  VALUE first_args;
  VALUE (*first_func)(ANYARGS);

  /* for GC */
  VALUE *machine_stack_start;
  VALUE *machine_stack_end;
  size_t machine_stack_maxsize;
#ifdef __ia64
  VALUE *machine_register_stack_start;
  VALUE *machine_register_stack_end;
  size_t machine_register_stack_maxsize;
#endif
  VALUE machine_regs;
  int mark_stack_len;

  /* statistics data for profiler */
  VALUE stat_insn_usage;

  /* tracer */
  VALUE event_hooks;
  VALUE trace_arg; /* trace information */

  /* fiber */
  VALUE fiber;
  VALUE root_fiber;
  VALUE root_jmpbuf;

  /* misc */
  int method_missing_reason;
  int abort_on_exception;
#ifdef USE_SIGALTSTACK
  void *altstack;
#endif
  unsigned long running_time_us;
} rb_thread_t;

#endif
```


```c:stackswap.c
#inlude "stackswap.h"

static VALUE
stackswap(VALUE self)
{
  rb_thread_t *_th;
  _th = (rb_thread_t*)rb_thread_current();
  // _th->stack とかでstackを参照
  return Qnil;
}

void
Init_stackswap(void)
{
  Stackswap = rb_define_module("Stackswap");
  rb_define_module_function(Stackswap, "stackswap", stackswap, 0);
}
```

Rakefileとかextconf.rbは省略します。

キモはrb_thread_tとrb_thread_current()で、前者はRubyのthreadを扱うための構造体、後者は現在のthreadをVALUEで取得する、RubyレベルでいうとThread.currentと等価なものです。

rb_thread_tはC APIとして提供してないので自前で用意してやる必要があります、また実際はrb_thread_t構造体の中でまた色々Ruby本体でごにょごにょあるんですが、はしょって全部VALUEで置き換えてます。まあ必要になったら都度用意する感じで。

まああとはstackを切り替えてオレオレ継続作ったりして遊んだりできっかな～、って感じですね。