Skip to content

Instantly share code, notes, and snippets.

@m3m0r7
Last active February 17, 2024 05:07
Show Gist options
  • Save m3m0r7/226e20c8115caf4a9d43b291861f978b to your computer and use it in GitHub Desktop.
Save m3m0r7/226e20c8115caf4a9d43b291861f978b to your computer and use it in GitHub Desktop.
<?php
$handle = fopen(__DIR__ . '/HelloWorld.yarv', 'r');
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11087
$magic = fread($handle, 4);
$majorVersion = unpack('V', fread($handle, 4))[1];
$minorVersion = unpack('V', fread($handle, 4))[1];
$fileSize = unpack('V', fread($handle, 4))[1];
$extraSize = unpack('V', fread($handle, 4))[1];
$iSeqListSize = unpack('V', fread($handle, 4))[1];
$globalObjectListSize = unpack('V', fread($handle, 4))[1];
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11096C5-L11096C17
$iSeqListOffset = unpack('V', fread($handle, 4))[1];
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11097
$globalObjectListOffset = unpack('V', fread($handle, 4))[1];
//var_dump(
// $magic, $majorVersion, $minorVersion,
// $fileSize, $extraSize, $iSeqListSize,
// $globalObjectListSize, $iSeqListOffset, $globalObjectListOffset,
//);
// iseq list offset の情報を取得する
$iseqListOffsets = [];
fseek($handle, $iSeqListOffset, SEEK_SET);
for ($i = 0; $i < $iSeqListSize; $i++) {
$iseqListOffsets[] = unpack('V', fread($handle, 4))[1];
}
// global object list offset の情報を取得する
$globalObjectListOffsets = [];
fseek($handle, $globalObjectListOffset, SEEK_SET);
for ($i = 0; $i < $globalObjectListSize; $i++) {
$globalObjectListOffsets[] = unpack('V', fread($handle, 4))[1];
}
//var_dump($iseqListOffsets, $globalObjectListOffsets);
function readSmallValue(): int
{
global $handle;
$offset = ftell($handle);
// ハミング重み
$ntzInt32 = function (int $x): int {
$x = ~$x & ($x - 1);
$x = ($x & 0x55555555) + ($x >> 1 & 0x55555555);
$x = ($x & 0x33333333) + ($x >> 2 & 0x33333333);
$x = ($x & 0x0F0F0F0F) + ($x >> 4 & 0x0F0F0F0F);
$x = ($x & 0x001F001F) + ($x >> 8 & 0x001F001F);
$x = ($x & 0x0000003F) + ($x >> 16 & 0x0000003F);
return $x;
};
$c = unpack('C', fread($handle, 1))[1];
$n = ($c & 1)
? 1
: (0 == $c ? 9 : $ntzInt32($c) + 1);
$x = $c >> $n;
if (0x7F === $x) {
$x = 1;
}
for ($i = 1; $i < $n; ++$i) {
$x <<= 8;
$x |= unpack('C', fread($handle, 1))[1];
}
fseek(
$handle,
$offset + $n,
SEEK_SET,
);
return $x;
}
// 0 番目のオフセットを参照
fseek($handle, $iseqListOffsets[0], SEEK_SET);
$type = readSmallValue();
$iseqSize = readSmallValue();
$bytecodeOffset = readSmallValue();
$bytecodeSize = readSmallValue();
$mnemonic = [
18 => 'putself',
21 => 'putstring',
51 => 'opt_send_without_block',
60 => 'leave',
];
fseek($handle, $iseqListOffsets[0] - $bytecodeOffset, SEEK_SET);
class Main
{
public function puts($string): void
{
echo $string;
}
}
function loadObject(int $offset)
{
global $handle;
$current = ftell($handle);
fseek($handle, $offset, SEEK_SET);
$byte = ord(fread($handle, 1));
// ビットに読み込むべき種類が格納されています。
$type = ($byte >> 0) & 0x1f;
$ret = null;
// 5 は文字列を読む処理
if ($type === 5) {
// NOTE: エンコーディング手法の情報。今回は使わない。
$encIndex = readSmallValue();
// 文字列長の取得
$len = readSmallValue();
$ret = fread($handle, $len);
} else {
throw new RuntimeException("未実装です ({$type})");
}
fseek($handle, $current, SEEK_SET);
return $ret;
}
// スタックの情報を管理するための変数です。
$stacks = [];
for ($codeIndex = 0; $codeIndex < $iseqSize; $codeIndex++) {
$code = readSmallValue();
switch ($mnemonic[$code] ?? null) {
case 'putself':
// Main コンテキストをスタックにプッシュさせます。
$stacks[] = new Main();
break;
case 'putstring':
// グローバルオブジェクトのオフセットの情報を取得します。
$pos = readSmallValue();
$objectPos = $globalObjectListOffsets[$pos];
// グローバルオブジェクトを取得します
$stacks[] = loadObject($objectPos);
break;
case 'opt_send_without_block':
// NOTE: puts の実装のみ。本来は call info entry から呼び出すべきメソッドが明らかになります。
$argument = array_pop($stacks);
$callee = array_pop($stacks);
// puts を呼び出し
$callee->puts($argument);
break;
case 'leave':
// NOTE: 今回の例では特に実装不要。返り値などが必要な場合に leave コマンドで操作する必要があります。
break;
}
}
<?php
$handle = fopen(__DIR__ . '/HelloWorld.yarv', 'r');
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11087
$magic = fread($handle, 4);
$majorVersion = unpack('V', fread($handle, 4))[1];
$minorVersion = unpack('V', fread($handle, 4))[1];
$fileSize = unpack('V', fread($handle, 4))[1];
$extraSize = unpack('V', fread($handle, 4))[1];
$iSeqListSize = unpack('V', fread($handle, 4))[1];
$globalObjectListSize = unpack('V', fread($handle, 4))[1];
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11096C5-L11096C17
$iSeqListOffset = unpack('V', fread($handle, 4))[1];
// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11097
$globalObjectListOffset = unpack('V', fread($handle, 4))[1];
//var_dump(
// $magic, $majorVersion, $minorVersion,
// $fileSize, $extraSize, $iSeqListSize,
// $globalObjectListSize, $iSeqListOffset, $globalObjectListOffset,
//);
// iseq list offset の情報を取得する
$iseqListOffsets = [];
fseek($handle, $iSeqListOffset, SEEK_SET);
for ($i = 0; $i < $iSeqListSize; $i++) {
$iseqListOffsets[] = unpack('V', fread($handle, 4))[1];
}
// global object list offset の情報を取得する
$globalObjectListOffsets = [];
fseek($handle, $globalObjectListOffset, SEEK_SET);
for ($i = 0; $i < $globalObjectListSize; $i++) {
$globalObjectListOffsets[] = unpack('V', fread($handle, 4))[1];
}
//var_dump($iseqListOffsets, $globalObjectListOffsets);
function readSmallValue(): int
{
global $handle;
$offset = ftell($handle);
// ハミング重み
$ntzInt32 = function (int $x): int {
$x = ~$x & ($x - 1);
$x = ($x & 0x55555555) + ($x >> 1 & 0x55555555);
$x = ($x & 0x33333333) + ($x >> 2 & 0x33333333);
$x = ($x & 0x0F0F0F0F) + ($x >> 4 & 0x0F0F0F0F);
$x = ($x & 0x001F001F) + ($x >> 8 & 0x001F001F);
$x = ($x & 0x0000003F) + ($x >> 16 & 0x0000003F);
return $x;
};
$c = unpack('C', fread($handle, 1))[1];
$n = ($c & 1)
? 1
: (0 == $c ? 9 : $ntzInt32($c) + 1);
$x = $c >> $n;
if (0x7F === $x) {
$x = 1;
}
for ($i = 1; $i < $n; ++$i) {
$x <<= 8;
$x |= unpack('C', fread($handle, 1))[1];
}
fseek(
$handle,
$offset + $n,
SEEK_SET,
);
return $x;
}
// 0 番目のオフセットを参照
fseek($handle, $iseqListOffsets[0], SEEK_SET);
$type = readSmallValue();
$iseqSize = readSmallValue();
$bytecodeOffset = readSmallValue();
$bytecodeSize = readSmallValue();
// Changed opcode (opt_send_without_block) from Ruby3.2 to Ruby3.3
$mnemonic = [
18 => 'putself',
21 => 'putstring',
53 => 'opt_send_without_block',
61 => 'leave',
];
fseek($handle, $iseqListOffsets[0] - $bytecodeOffset, SEEK_SET);
class Main
{
public function puts($string): void
{
echo $string;
}
}
function loadObject(int $offset)
{
global $handle;
$current = ftell($handle);
fseek($handle, $offset, SEEK_SET);
$byte = ord(fread($handle, 1));
// ビットに読み込むべき種類が格納されています。
$type = ($byte >> 0) & 0x1f;
$ret = null;
// 5 は文字列を読む処理
if ($type === 5) {
// NOTE: エンコーディング手法の情報。今回は使わない。
$encIndex = readSmallValue();
// 文字列長の取得
$len = readSmallValue();
$ret = fread($handle, $len);
} else {
throw new RuntimeException("未実装です ({$type})");
}
fseek($handle, $current, SEEK_SET);
return $ret;
}
// スタックの情報を管理するための変数です。
$stacks = [];
for ($codeIndex = 0; $codeIndex < $iseqSize; $codeIndex++) {
$code = readSmallValue();
switch ($mnemonic[$code] ?? null) {
case 'putself':
// Main コンテキストをスタックにプッシュさせます。
$stacks[] = new Main();
break;
case 'putstring':
// グローバルオブジェクトのオフセットの情報を取得します。
$pos = readSmallValue();
$objectPos = $globalObjectListOffsets[$pos];
// グローバルオブジェクトを取得します
$stacks[] = loadObject($objectPos);
break;
case 'opt_send_without_block':
// NOTE: puts の実装のみ。本来は call info entry から呼び出すべきメソッドが明らかになります。
$argument = array_pop($stacks);
$callee = array_pop($stacks);
// puts を呼び出し
$callee->puts($argument);
break;
case 'leave':
// NOTE: 今回の例では特に実装不要。返り値などが必要な場合に leave コマンドで操作する必要があります。
break;
}
}
@m3m0r7
Copy link
Author

m3m0r7 commented Sep 10, 2023

For example; implementation of the call info entry as following:

<?php

$handle = fopen(__DIR__ . '/HelloWorld.yarv', 'r');

// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11087
$magic = fread($handle, 4);
$majorVersion = unpack('V', fread($handle, 4))[1];
$minorVersion = unpack('V', fread($handle, 4))[1];
$fileSize = unpack('V', fread($handle, 4))[1];
$extraSize = unpack('V', fread($handle, 4))[1];
$iSeqListSize = unpack('V', fread($handle, 4))[1];
$globalObjectListSize = unpack('V', fread($handle, 4))[1];

// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11096C5-L11096C17
$iSeqListOffset = unpack('V', fread($handle, 4))[1];

// see: https://github.com/ruby/ruby/blob/2f603bc4/compile.c#L11097
$globalObjectListOffset = unpack('V', fread($handle, 4))[1];


//var_dump(
//    $magic, $majorVersion, $minorVersion,
//    $fileSize, $extraSize, $iSeqListSize,
//    $globalObjectListSize, $iSeqListOffset, $globalObjectListOffset,
//);

// iseq list offset の情報を取得する
$iseqListOffsets = [];
fseek($handle, $iSeqListOffset, SEEK_SET);
for ($i = 0; $i < $iSeqListSize; $i++) {
    $iseqListOffsets[] = unpack('V', fread($handle, 4))[1];
}
// global object list offset の情報を取得する
$globalObjectListOffsets = [];
fseek($handle, $globalObjectListOffset, SEEK_SET);
for ($i = 0; $i < $globalObjectListSize; $i++) {
    $globalObjectListOffsets[] = unpack('V', fread($handle, 4))[1];
}

//var_dump($iseqListOffsets, $globalObjectListOffsets);

function readSmallValue(): int
{
    global $handle;
    $offset = ftell($handle);


    // ハミング重み
    $ntzInt32 = function (int $x): int {
        $x = ~$x & ($x - 1);
        $x = ($x & 0x55555555) + ($x >> 1 & 0x55555555);
        $x = ($x & 0x33333333) + ($x >> 2 & 0x33333333);
        $x = ($x & 0x0F0F0F0F) + ($x >> 4 & 0x0F0F0F0F);
        $x = ($x & 0x001F001F) + ($x >> 8 & 0x001F001F);
        $x = ($x & 0x0000003F) + ($x >> 16 & 0x0000003F);

        return $x;
    };

    $c = unpack('C', fread($handle, 1))[1];

    $n = ($c & 1)
        ? 1
        : (0 == $c ? 9 : $ntzInt32($c) + 1);

    $x = $c >> $n;

    if (0x7F === $x) {
        $x = 1;
    }
    for ($i = 1; $i < $n; ++$i) {
        $x <<= 8;
        $x |= unpack('C', fread($handle, 1))[1];
    }

    fseek(
        $handle,
        $offset + $n,
        SEEK_SET,
    );

    return $x;
}

// 0 番目のオフセットを参照
fseek($handle, $iseqListOffsets[0], SEEK_SET);
$type = readSmallValue();
$iseqSize = readSmallValue();

$bytecodeOffset = readSmallValue();
$bytecodeSize = readSmallValue();

// $bytecodeSize の真下に追加
$paramFlags = readSmallValue(); // 使用しない
$paramSize = readSmallValue(); // 使用しない
$paramLeadNum = readSmallValue(); // 使用しない
$paramOptNum = readSmallValue(); // 使用しない
$paramRestStart = readSmallValue(); // 使用しない
$paramPostStart = readSmallValue(); // 使用しない
$paramPostNum = readSmallValue(); // 使用しない
$paramBlockStart = readSmallValue(); // 使用しない
$paramOptTableOffset = readSmallValue(); // 使用しない
$paramKeywordOffset = readSmallValue(); // 使用しない
$locationPathObjIndex = readSmallValue(); // 使用しない
$locationBaseLabelIndex = readSmallValue(); // 使用しない
$locationLabelIndex = readSmallValue(); // 使用しない
$locationFirstLineNo = readSmallValue(); // 使用しない
$locationNodeId = readSmallValue(); // 使用しない
$locationCodeLocationBegPosLineNo = readSmallValue(); // 使用しない
$locationCodeLocationBegPosColumn = readSmallValue(); // 使用しない
$locationCodeLocationEndPosLineNo = readSmallValue(); // 使用しない
$locationCodeLocationEndPosColumn = readSmallValue(); // 使用しない
$insnsInfoBodyOffset = readSmallValue(); // 使用しない
$insnsInfoPositionsOffset = readSmallValue(); // 使用しない
$insnsInfoSize = readSmallValue(); // 使用しない
$localTableOffset = readSmallValue(); // 使用しない
$catchTableSize = readSmallValue(); // 使用しない
$catchTableOffset = readSmallValue(); // 使用しない
$parentISeqIndex = readSmallValue(); // 使用しない
$localISeqIndex = readSmallValue(); // 使用しない
$mandatoryOnlyIseqIndex = readSmallValue(); // 使用しない
$ciEntriesOffset = readSmallValue();
$outerVariablesOffset = readSmallValue(); // 使用しない
$variableFlipCount = readSmallValue(); // 使用しない
$localTableSize = readSmallValue(); // 使用しない

$ivcSize = readSmallValue(); // 使用しない
$icvArcSize = readSmallValue(); // 使用しない
$iseSize = readSmallValue(); // 使用しない
$icSize = readSmallValue(); // 使用しない

$ciSize = readSmallValue();

// $ciSize 変数の真下に追加
// call info entries の読み込み
fseek($handle, $iseqListOffsets[0] - $ciEntriesOffset, SEEK_SET);

$callInfoEntries = [];
for ($i = 0; $i < $ciSize; $i++) {
    // メソッド名
    $methodName = loadObject($globalObjectListOffsets[readSmallValue()]);

    // メソッドの種別(今回は使わない)
    $flag = readSmallValue();

    // 引数の数
    $argCount = readSmallValue();
    $callInfoEntries[] = [$methodName, $argCount];
}

var_dump($callInfoEntries);

$mnemonic = [
    18 => 'putself',
    21 => 'putstring',
    51 => 'opt_send_without_block',
    60 => 'leave',
];

// Ruby 3.3 の場合は以下に差し替え
// $mnemonic = [
//     18 => 'putself',
//     21 => 'putstring',
//     53 => 'opt_send_without_block',
//     61 => 'leave',
// ];

fseek($handle, $iseqListOffsets[0] - $bytecodeOffset, SEEK_SET);

class Main
{
    public function puts($string): void
    {
        echo $string;
    }
}

function loadObject(int $offset)
{
    global $handle;
    $current = ftell($handle);
    fseek($handle, $offset, SEEK_SET);

    $byte = ord(fread($handle, 1));

    // ビットに読み込むべき種類が格納されています。
    $type = ($byte >> 0) & 0x1f;
    $ret = null;

    // 5 は文字列を読む処理。20 はシンボルを読む処理だが,今回は文字列を読み込むのと同等で問題ない
    if ($type === 5 || $type === 20) {
        // NOTE: エンコーディング手法の情報。今回は使わない。
        $encIndex = readSmallValue();

        // 文字列長の取得
        $len = readSmallValue();

        $ret = fread($handle, $len);
    } else {
        throw new RuntimeException("未実装です ({$type})");
    }

    fseek($handle, $current, SEEK_SET);
    return $ret;
}

// スタックの情報を管理するための変数です。
$stacks = [];
for ($codeIndex = 0, $ciIndex = 0; $codeIndex < $iseqSize; $codeIndex++) {
    $code = readSmallValue();
    switch ($mnemonic[$code] ?? null) {
        case 'putself':
            // Main コンテキストをスタックにプッシュさせます。
            $stacks[] = new Main();
            break;
        case 'putstring':
            // グローバルオブジェクトのオフセットの情報を取得します。
            $pos = readSmallValue();
            $objectPos = $globalObjectListOffsets[$pos];

            // グローバルオブジェクトを取得します
            $stacks[] = loadObject($objectPos);
            break;
        case 'opt_send_without_block':
            [$methodName, $argCount] = $callInfoEntries[$ciIndex++];
            $arguments = [];
            for ($k = 0; $k < $argCount; $k++) {
                $arguments[] = array_pop($stacks);
            }

            $callee = array_pop($stacks);

            // メソッドを呼び出し
            $callee->{$methodName}(...$arguments);
            break;
        case 'leave':
            // NOTE: 今回の例では特に実装不要。返り値などが必要な場合に leave コマンドで操作する必要があります。
            break;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment