V8源碼邊緣試探-黑魔法指針偏移

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/07/16/9317297.html
-Advertisement-
Play Games

這博客是越來越難寫了,參考資料少,難度又高,看到什麼寫什麼吧! 眾多周知,在JavaScript中有幾個基本類型,包括字元串、數字、布爾、null、undefined、Symbol,其中大部分都可以在我之前那篇博客(https://www.cnblogs.com/QH-Jimmy/p/9212923 ...


  這博客是越來越難寫了,參考資料少,難度又高,看到什麼寫什麼吧!

  眾多周知,在JavaScript中有幾個基本類型,包括字元串、數字、布爾、null、undefined、Symbol,其中大部分都可以在我之前那篇博客(https://www.cnblogs.com/QH-Jimmy/p/9212923.html)中找到,均繼承於Primitive類。但是仔細看會發現少了兩個,null和undefined呢?這一節,就來探索一下,V8引擎是如何處理null、undefined兩種類型的。

  在沒有看源碼之前,我以為是這樣的:

class Null : public Primitive {
public:
    // Type testing.
    bool IsNull() const { return true; }
    // ...
}

  然而實際上沒有這麼簡單粗暴,V8對null、undefined(實際上還包括了true、false、空字元串)都做了特殊的處理。

  回到故事的起點,是我在研究LoadEnvironment函數的時候發現的。上一篇博客其實就是在講這個方法,包裝完函數名、函數體,最後一步就是配合函數參數來執行函數了,代碼如下:

// Bootstrap internal loaders
Local<Value> bootstrapped_loaders;
if (!ExecuteBootstrapper(env, loaders_bootstrapper,
                        arraysize(loaders_bootstrapper_args),
                        loaders_bootstrapper_args,
                        &bootstrapped_loaders)) {
return;
}

  這裡的參數分別為:

1、env => 當前V8引擎的環境變數,包含Isolate、context等。

2、loaders_bootstrapper => 函數體

3、arraysize(loaders_bootstrapper_args) => 參數長度,就是4

4、loaders_bootstrapper_args => 參數數組,包括process對象及3個C++內部方法

5、&bootstrapped_loaders => 一個局部變數指針

  參數是啥並不重要,進入方法,源碼如下:

static bool ExecuteBootstrapper(Environment* env, Local<Function> bootstrapper,
                                int argc, Local<Value> argv[],
                                Local<Value>* out) {
  bool ret = bootstrapper->Call(
      env->context(), Null(env->isolate()), argc, argv).ToLocal(out);
  if (!ret) {
    env->async_hooks()->clear_async_id_stack();
  }

  return ret;
}

  看起來就像JS裡面的call方法,其中函數參數包括context、null、形參數量、形參,當時看到Null覺得比較好奇,就仔細的看了一下實現。

 

  這個方法其實很簡單,但是實現的方式非常有意思,源碼如下:

Local<Primitive> Null(Isolate* isolate) {
    typedef internal::Object* S;
    typedef internal::Internals I;
    // 檢測當前V8引擎實例是否存活
    I::CheckInitialized(isolate);
    // 核心方法
    S* slot = I::GetRoot(isolate, I::kNullValueRootIndex);
    // 類型強轉 直接是Primitive類而不是繼承
    return Local<Primitive>(reinterpret_cast<Primitive*>(slot));
}

  只有GetRoot是真正生成null值的地方,註意第二個參數 I::kNullValueRootIndex ,這是一個靜態整形值,除去null還有其他幾個,所有的類似值定義如下:

static const int kUndefinedValueRootIndex = 4;
static const int kTheHoleValueRootIndex = 5;
static const int kNullValueRootIndex = 6;
static const int kTrueValueRootIndex = 7;
static const int kFalseValueRootIndex = 8;
static const int kEmptyStringRootIndex = 9;

  上面的數字就是區分這幾個類型的關鍵所在,繼續進入GetRoot方法:

V8_INLINE static internal::Object** GetRoot(v8::Isolate* isolate,int index) {
    // 獲取當前isolate地址併進行必要的空間指針偏移
    // static const int kIsolateRootsOffset = kExternalMemoryLimitOffset + kApiInt64Size + kApiInt64Size + kApiPointerSize + kApiPointerSize;
    uint8_t* addr = reinterpret_cast<uint8_t*>(isolate) + kIsolateRootsOffset;
    // 根據上面的數字以及當前操作系統指針大小進行偏移
    // const int kApiPointerSize = sizeof(void*);  // NOLINT
    return reinterpret_cast<internal::Object**>(addr + index * kApiPointerSize);
}

  這個方法就對應了標題,指針偏移。

  實際上根本不存在一個正規的null類來生成一個對應的對象,而只是把一個特定的地址當成一個null值。

  敢於用這個方法,是因為對於每一個V8引擎來說isolate對象是獨一無二的,所以在當前引擎下,獲取到的isolate地址也是唯一的。

  如果還不明白,我這個靈魂畫手會讓你明白,超級簡單:

  最後返回一個地址,這個地址就是null,強轉成Local<Primitive>也只是為了垃圾回收與類型區分,實際上並不關心這個指針指向什麼,因為null本身不存在任何方法可以調用,大多數情況下也只是用來做變數重置。

  就這樣,只用了很小的空間便生成了一個null值,並且每一次獲取都會返回同一個值。

 

  驗證的話就很簡單了,隨意的在node啟動代碼裡加一段:

auto test = Null(env->isolate());

  然後看局部變數的調試框,當前isolate的地址如下:

  第一次指針偏移後,addr的地址為:

  通過簡單計算,這個差值是72(16進位的48),跟第一次偏移量大小一致,這裡根本不關心指針指向什麼東西,所以字元無效也沒事。

  第二次偏移後,得到的null地址為:

  通過計算得到差值為48(16進位的30),算一算,剛好是6*8。

  最後對這個地址進行強轉,返回一個Local<Primitive>類型的null對象。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 一個優秀的應用不僅僅是要有吸引人的功能和交互,同時在性能上也有很高的要求。運行Android系統的手機,雖然配置在不斷的提升,但仍舊無法和PC相比,無法做到PC那樣擁有超大的記憶體以及高性能的CPU,因此在開發Android應用程式時也不可能無限制的使用CPU和記憶體,如果對CPU和記憶體使用不當也 ...
  • 一,效果圖。 二,代碼。index.html文件如下所示。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo</title> <meta name="viewport" content="initial-scale=1, ...
  • Git Bash提供了一種方式可以在Windows下執行Linux命令,如何將其結合在VSCode中使用呢? 兩種方式: 1. 將 Git bash目錄比如D:\Git\bin 添加到環境變數中,就可以在VSCode終端輸入bash進入git的bash模式了; 同理,在bash模式下輸入cmd即可返 ...
  • 第1章 課程介紹 1-1 Angular打造企業平臺導學 1-2 環境搭建第2章 用 Angular Material 組件打造頁面 2-1 項目工程結構介紹 2-2 UI整體佈局 2-3 Material介紹 2-4 MdIcon 組件 2-5 Input 組件 2-6 Card 和 Button ...
  • 作用 HTML 標題可以用來呈現文檔結構,設置得當的標題有利於用戶瀏覽您的網頁 標題 標題(Heading)是通過 <H1>-<H6> 標簽進行定義的 <h1>最大的 標題</h1> <h6>最小的 標題</h6> <hr>水平線 創造出來的是一條水平分割線,可以使文檔在視覺上分成部分 <p>hr水 ...
  • 學習https://www.w3schools.com/html/default.asp 一、概念 HTML 指的是超文本標記語言 (Hyper Text Markup Language) HTML 不是一種編程語言,而是一種標記語言 (markup language) 標記語言是一套標記標簽 (m ...
  • 這個問題在網路上有很多答案,但是真正能解決的寥寥無幾!接下來我就來嘗試一下網路上瘋傳的幾種方法。準備好了嗎?我要開車了!!! PS:以下實驗上傳到github的demo採取導入本地css,js和網路上css,js的方法進行測試 demo目錄結構 preview.html preview.css pr ...
  • Swiper是一款開源、免費、功能十分強大的移動端內容觸摸滑動插件,目前的最新版本是Swiper4。Swiper主要面向的是手機、平板電腦等移動設備,幫助開發者輕鬆實現觸屏焦點圖、觸屏Tab切換、觸屏多圖切換等常用效果。本文簡單介紹一下Swiper的使用方法。 下載和安裝Swiper 首先我們需要下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...