Давайте разберемся с Chrome V8 (глава 18): подробности о байт-коде.

Добро пожаловать в другие главы Давайте разберемся с Chrome V8

Байт-код реализован с помощью CodeStubAssembler (CSA), который можно грубо рассматривать как сборку. Байт-код загружается в десериализованном виде во время запуска V8 без таблицы символов. Итак, я хочу вам сказать: CAS непонятен в статическом анализе. Более того, мы не можем получить исходный код байт-кода при отладке. В этой статье будет рассказано о том, как отладить байт-код на уровне сборки и посмотреть подробности его выполнения.

На рис. 1 показан интерпретатор байт-кодов. Рисунок достаточно подробен для разработчиков JavaScript. Но для учащихся V8 на рисунке 1 не хватает многих деталей, таких как: запуск зажигания, загрузка байт-кода и отправка. Давайте отладим байт-код, чтобы углубиться в эти детали.

1. Подготовка

Примечание: перед отладкой вам лучше понять фреймы стека, кодировку байт-кодов и регистров, см. здесь.

Ниже показана функция Invoke, которая является последней функцией C++ перед отладкой.

Строки с 14 по 23 инициализируют JSFunction, который будет выполняться, а именно ваш код JavaScript. Ниже приведены пять важных членов, которые вы должны помнить. Эти участники могут помочь вам определить вашу позицию в области отладки. Они постоянны в одном контексте отладки, но будут отличаться в другом отладочном контексте.

(1) Строка 7, код, это указатель Builtin::JSEntry, теперь значение равно 1FA 0E06 ED30 (в моем контексте отладки).

(2) Строка 18, stub_entry, это вход в Ignition. Ниже приведена функция, которая получает stub_entry из Builtin::JSEntry.

Запись 1FA 1326 1840.

(3) Line21, func, это адрес JSFunction, а именно вашего кода JavaScript. Значение 16 2BD8 15A9.

(4) Адрес Builtin::InterpreterEntryTrampoline: 52 61C0 8A41.

(5) Таблица dispatch_table имеет вид 1FA 0E08 CFB0.

На рисунке 2 показан стек вызовов, который переходит в байт-код.

2. Отладка байт-кода

Давайте начнем отлаживать байт-код, войдя в stub_entry.Call(). Извините, я не могу пройтись построчно, так как ассемблерный код не имеет контекста. Я думаю, что лучший способ — дать важные состояния во время выполнения Ignition.

На рисунке 3 регистр RCX — это 1FA 1326 1840 (stub_entry). Как я уже упоминал, stub_entry — это вход в Ignition.

На рисунке 4 регистр RBX — это 1FA0E068A00, который является первым аргументом stub_entry.Call(), то есть isolate-›isolate_data()-›isolate_root().

Рисунок 5 показывает движение R8 и вызов RSI. Регистр R8 — это func, а RSI — это stub_entry, ниже приведена функция, на которую указывает stub_entry.

В строках с 12 по 37 мы видим, что они совпадают с ассемблерным кодом на рис. 6. На самом деле выполняется Generate_JSEntryVariant. Builtin:JSEntry отвечает за организацию аргументов по стандарту std::call. Позже эти аргументы будет использовать функция Builtin::InterpreterEntryTrampoline.

Builtin::InterpreterEntryTrampoline выполняет поиск в таблице dispatch_table, чтобы найти целевой байт-код и вызвать его, как показано на рис. 7.

Давайте рассмотрим диспетчеризацию более подробно, см. рис. 7.

  • метка 1: машинный регистр R15 — это таблица dispatch_table.
  • метка 2: вычислить адрес целевого байт-кода, т. е. dispatch_table + кодировку байт-кода.
  • отметка 3: вызвать целевой байт-код.

В моем случае целевой байт-код — LdaConstant.

Когда LdaConstant завершает работу, просмотрите таблицу dispatch_table, чтобы найти следующий байт-код, как показано на рисунке 8.

На уровне сборки мы можем увидеть запуск Ignition и выполнение байт-кода, что может помочь нам лучше понять интерпретатор V8.

Хорошо, на этом мы закончили. Увидимся в следующий раз, берегите себя!

Пожалуйста, свяжитесь со мной, если у вас есть какие-либо проблемы. WeChat: qq9123013 Электронная почта: [email protected]

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.