What is It?
Ignition là một trình thông dịch dựa trên thanh ghi của V8. Các thanh ghi mà nó sử dụng không thật sự là thanh ghi của máy tính mà là các slot cụ thể ở trong register file mà được cấp phát ở trên stack.
Bytecode Handlers
Ignition chứa một tập các bytecode handler dùng để thực thi bytecode và sẽ được biên dịch bởi V8 - Turbo Fan khi trình duyệt được biên dịch. Từng handler sẽ xử lý từng bytecode cụ thể và sẽ dispatch đến handler tiếp theo.
Khi V8 tạo ra một isolate mới, nó sẽ nạp tất cả các handler từ một snapshot file được tạo ra trong quá trình build.
Ngoài ra, isolate còn có một global dispatch table chứa các con trỏ đến các Code
object (là các object chứa code) của các bytecode handler và được đánh index bằng giá trị của bytecode.
Generating Bytecode
Ignition sẽ lấy abtract syntax tree (AST) từ parser và duyệt qua từng node để tạo ra các bytecode tương ứng. V8 sử dụng hàm GenerateBytecode
thuộc lớp BytecodeGenerator
để làm điều này.
Mỗi hàm của JavaScript đều được V8 biểu diễn bằng một JSFunction
object. Thuộc tính shared
có kiểu là SharedFunctionInfo
(SFI) trong JSFunction
sẽ lưu bytecode và được chia sẻ bởi những instance khác nhau của hàm.
Trong quá trình tạo ra bytecode, BytecodeGenerator
cũng sẽ cấp phát các thanh ghi cần dùng trong quá trình thực thi của Ignition vào một register file cho các biến cục bộ, các con trỏ của context và các giá trị dùng tạm.
Bytecode
Xét hàm sau:
Bytecode của nó sẽ là:
Các toán hạng chẳng hạn như a0
và r0
là các thanh ghi. Với:
a0
tương ứng với đối số đầu tiên. Các đối số tiếp theo sẽ làa1
,a2
,a3
, etc.r0
tương ứng với biến cục bộ.
Ngoài những thanh ghi thông thường, Ignition còn có thanh ghi accumulator (hay acc
), đóng vai trò như là thanh ghi lưu trữ giá trị trung gian trong quá trình tính toán và được ngầm định ở đa số các bytecode. Ví dụ, bytecode Return
sẽ trả về giá trị của thanh ghi acc
.
Info
Việc sử dụng
acc
ngầm định giúp giảm kích thước bytecode.
Giải thích đoạn bytecode trên:
LdaSmi [1]
lưu số1
vàoacc
:Ld
là dạng rút gọn của loada
là để chỉ accumulatorSmi
là viết tắt của small integer
Star0
lưu giá trị củaacc
(1
) vàor0
:St
là dạng rút gọn của storea
là để chỉ accumulatorr0
là để chỉ thanh ghir0
GetNamedProperty a0, [0], [1]
lưu thuộc tính ở index 0 ([0]
) củaa0
vàoacc
. Giá trị[1]
là feedback vector mà sẽ được dùng bởi Turbo FanAdd r0, [0]
cộngr0
vớiacc
và lưu giá trị vàoacc
([0]
là feedback vector)Return
trả về giá trị củaacc
.
Seealso
Có thể xem danh sách các bytecode ở tập tin bytecodes.h.
Stack Frame Generation and Execution
Sau khi bytecode được tạo ra, thuộc tính code_entry_point
sẽ trỏ đến một built-in stub có tên là InterpreterEntryTrampoline
.
Lưu ý, InterpreterEntryTrampoline
chỉ được gán cho code_entry_point
khi một hàm của JavaScript được gọi (nếu hàm không được gọi thì stub của nó sẽ là CompileLazy
và bytecode sẽ không được sinh ra). InterpreterEntryTrampoline
chịu trách nhiệm thiết lập stack frame cho Ignition và dispatch bytecode đầu tiên trong hàm đến bytecode handler của Ignition.
Trong quá trình thiết lập stack frame, InterpreterEntryTrampoline
sẽ cấp phát không gian bộ nhớ ở trên stack cho register file và ghi giá trị undefined
cho tất cả các thanh ghi để garbage collector không xem các thanh ghi này là bất hợp lệ khi nó duyệt qua stack.
Bố cục của stack frame sau quá trình thiết lập của InterpreterEntryTrampoline
:
Ignition stack frame sẽ bao gồm:
Context
: context hiện tại của isolate.$PC
: pointer counter của caller.JSFunction
: con trỏ trỏ đếnJSFunction
của hàm.
Cấu trúc của JSFunction
:
Có thể thấy, JSFunction
bao gồm:
Context
: context của hàm, chứa các biến cục bộ và đối tượngthis
.SharedFunctionInfo
: chứa bytecode.Feedback Vector
: chứa các inline cache của hàm.
Thanh ghi accumulator sẽ được lưu trong đối tượng được trỏ đến bởi Frame Pointer
:
Con trỏ BytecodeArray
sẽ trỏ đến đối tượng có chứa chuỗi các bytecode của một hàm cụ thể ở trên stack frame. Bên trong BytecodeArray
còn có một con trỏ có tên là Constant Pool Pointer
và sẽ trỏ đến các đối tượng ở trên heap mà được xem như là hằng số và có thể được tái sử dụng giữa các bytecode:
Execution
Để thực thi một bytecode, Ignition sẽ cần phải tìm handler tương ứng của bytecode để dispatch.
Xét một BytecodeArray
sau:
Dòng bytecode đầu tiên có giá trị là 13 00
. Trong đó, 0x13
là giá trị của bytecode LdaConstant
ở dạng thập lục phân. Dựa vào giá trị này, Ignition sẽ tra ở trong global dispatch table để tìm handler tương ứng. Sau đó, Ignition sẽ dùng hàm SetBytecodeHandler
với đối số là bytecode, các toán hạng và handler để thiết lập handler cho bytecode.
Sau đó, Ignition sẽ dispatch bytecode đến cho bytecode handler để thực thi hàm.
Related
Resources
- (448) BlinkOn 6 Day 1 Talk 2: Ignition - an interpreter for V8 - YouTube
- Ignition: An Interpreter for V8 [BlinkOn] - Google Trang trình bày
- Understanding V8’s Bytecode. V8 is Google’s open source JavaScript… | by Franziska Hinkelmann | DailyJS | Medium
- https://jhalon.github.io/chrome-browser-exploitation-2/