これもフィヨルドブートキャンプの生徒さんの質問からふと思いついた、ちょっとした遊びですが。
(そして、書いてある内容に誤解があったら優しく教えてください)
p Object.new => #<Object:0x000055959ddf1910>
Rubyのオブジェクトのinspect表示のデフォルトで出てくる、この16進数は、このオブジェクトが置かれているメモリアドレスのことだと知られている。
では、実際にこのメモリアドレスにオブジェクトが置かれていることを確かめるには?
さて、以下のコードはLinuxで動かすことにする。
String オブジェクトで試してみる。と言っても、StringのinspectはObjectに定義されたものではなく、自分のクラスで定義しているので、まずはそれを「無効にする」。以下のような方法で Object#inspect
を呼ぶように変更できる。
class String # 元に戻せるよう、本来のinspectを別名で保存 alias orig_inspect inspect def inspect super end end
String のinspect表示が変わっているのを確かめる。
str = "Hola, mundo" => #<String:0x000055959d972038>
このアドレスにある自分のメモリの状況を見てみたい。Linuxであれば、 /proc/self/mem
を読み込むことで確認ができる。
このprocってなんだろう? と詳しく知りたい向きには man 5 proc
などを読んでもらうこととして、 /proc
というファイルシステムの下からプロセスの情報が取得できるというぐらいの理解で一旦は先に進もう。
ということで /proc/self/mem
を読み込んで 0x000055959d972038
分だけseekする。Rubyだとこの表現のまま数値と認識してくれるので便利だね。
mem = open("/proc/self/mem") mem.seek 0x000055959d972038
この先の作業がわかりづらいので、String#inspectの定義を元に戻す。
class String alias inspect orig_inspect end
さて、メモリからとりあえず32バイト(なお、この数値は勘で決めました)ほど読み込んでみた。
dump = mem.read 32 => "e\xC0R\x00\x00\x00\x00\x00`hT\x9D\x95U\x00\x00Hola, mundo\x00\x00\x00\x00\x00"
なんとなく、先ほどの文字列 Hola, mundo
が格納されているような気がするが、実際どういうレイアウトになっているかというと。
Ruby 3.0.2 では文字列を表す構造体 RString
は次の定義:
struct RString { struct RBasic basic; union { struct { long len; char *ptr; union { long capa; VALUE shared; } aux; } heap; char ary[RSTRING_EMBED_LEN_MAX + 1]; } as; };
RBasicは次の定義:
struct RUBY_ALIGNAS(SIZEOF_VALUE) RBasic { VALUE flags; /**< @see enum ::ruby_fl_type. */ const VALUE klass; #ifdef __cplusplus // ... は関係ないので飛ばします #endif };
VALUE はだいたい uintptr_t
のことらしい。
typedef uintptr_t VALUE;
ビルドの環境によって当然若干変わってくるが、筆者の x86-64 なLinux環境ではこの定義と考えて良いだろう。
ということは、メモリから取り出したバイナリ列を、とりあえず以下の要領で unpack すれば。
# VALUE VALUE char[] として unpack する dump.unpack "Q! Q! Z*" => [5423205, 94101078042720, "Hola, mundo"]
... というようにメモリ上の文字列を取り出せた。
ちなみに、 RSTRING_EMBED_LEN_MAX
という定数があることから、ある程度以上長い文字列だとRString構造体の中には収まらないことが想像される。
実際にやってみよう。
class String; def inspect; super; end; end str2 = "very long string " * 100 => #<String:0x000055959de10180> # 戻す class String; alias inspect orig_inspect; end
0x000055959de10180
を読み込む。
mem.close mem = open("/proc/self/mem") mem.seek 0x000055959de10180 dump = mem.read 32 => "e P\x00\x00\x00\x00\x00`hT\x9D\x95U\x00\x00\xA4\x06\x00\x00\x00\x00\x00\x00`\xE2\xE7\x9D\x95U\x00\x00"
上述した RString, RBasic の定義を参考にして、こういうレイアウトだとしてunpackしてみる。
struct _RString { VALUE flags; VALUE klass; struct { long len; char *ptr; // ... } heap }
dump.unpack "Q! Q! l! Q!" => [5251173, 94101078042720, 1700, 94101087707744]
1700
というのが文字列の長さにちゃんと対応している。
str2.size
=> 1700
で、アドレス 94101087707744
*1から 1700 バイト読み込み、そこに当該の文字列がいることを確認することができる。
mem.seek 94101087707744 mem.read 1700 => "very long string very long string very long string... "