<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    《Chrome V8 源碼》41. Runtime_StringTrim 源碼、觸發條件

    VSole2021-12-31 16:48:26

    介紹

    Runtime 是一系列采用 C++ 語言編寫的功能方法,它實現了大量 JavaScript 運行期間需要的 native 功能。接下來幾篇文章將介紹一些 Runtime 方法。本文分析 Runtime_StringTrim 方法的源碼和重要數據結構,講解 Runtime_StringTrim 方法的觸發條件。

    注意: Runtime 方法的加載、調用以及 RUNTIME_FUNCTION 宏模板請參見第十六篇文章。—allow-natives-syntax 和 %-prefix 不是本文的講解重點。

    StringTrim 測試用例

    編寫可以觸發特定的 V8 內部功能的 JavaScript 測試用例,可以幫助我們更好地理解 V8 的內部工作原理,達到事半功倍的效果。下面講解 Runtime_StringTrim 測試用例的編寫思路:

    字符串的 Trim 方法由 TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) 函數實現,這個函數設置了一些字符串檢測條件,如果滿足檢測條件就會啟動 Runtime_StringTrim 方法。因此,我們需要從 TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) 開始分析,源碼如下:

    1.  TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) {2.    TNode<IntPtrT> argc =3.        ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount));4.    TNode<Context> context = CAST(Parameter(Descriptor::kContext));5.    Generate(String::kTrim, "String.prototype.trim", argc, context);6.  }7.  //分隔..................8.  void StringTrimAssembler::Generate(String::TrimMode mode,9.                                     const char* method_name, TNode<IntPtrT> argc,10.                                     TNode<Context> context) {11.    Label return_emptystring(this), if_runtime(this);12.    CodeStubArguments arguments(this, argc);13.    TNode<Object> receiver = arguments.GetReceiver();14.    TNode<String> const string = ToThisString(context, receiver, method_name);15.    TNode<IntPtrT> const string_length = LoadStringLengthAsWord(string);16.    ToDirectStringAssembler to_direct(state(), string);17.    to_direct.TryToDirect(&if_runtime);18.    TNode<RawPtrT> const string_data = to_direct.PointerToData(&if_runtime);19.    TNode<Int32T> const instance_type = to_direct.instance_type();20.    TNode<BoolT> const is_stringonebyte =21.        IsOneByteStringInstanceType(instance_type);22.    TNode<IntPtrT> const string_data_offset = to_direct.offset();23.    TVARIABLE(IntPtrT, var_start, IntPtrConstant(0));24.    TVARIABLE(IntPtrT, var_end, IntPtrSub(string_length, IntPtrConstant(1)));25.  //省略................26.    arguments.PopAndReturn(27.        SubString(string, var_start.value(),28.                  IntPtrAdd(var_end.value(), IntPtrConstant(1))));29.    BIND(&if_runtime);30.    arguments.PopAndReturn(31.        CallRuntime(Runtime::kStringTrim, context, string, SmiConstant(mode)));32.    BIND(&return_emptystring);33.    arguments.PopAndReturn(EmptyStringConstant());34.  }
    

    上述代碼中,第 5 行代碼調用 Generate() 方法;

    第 11 行代碼定義 runtime 標簽;

    第 14-15 行代碼獲取字符串以及它的長度;

    第 16-17 行 TryToDirect 把字符串轉換為直接字符串,如果 TryToDirect 失敗將采用 Runtime 方式處理;

    第 29 行綁定 runtime 標簽;

    第 31 行調用 Runtime::kStringTrim 方法。

    runtime 標簽僅在第 17 行被使用一次,由此我們可知:構造一段 “TryToDirect 失敗” 的 JavaScript 源碼是觸發 Runtime 的條件。TryToDirect() 的原理和失敗條件在之前的文章中講過。V8 的字符串類型包括:SeqString、ConsString、SliceString、ThinString、ExternalString。直接給出結論:一個單字節串和兩個雙字節串組成的 ConsString 串可以導致 “TryToDirect 失敗”,源碼如下:

    var str1 = "  ~~~"; //前面有空格var str2 = "彼其之子、美如玉。";var str3 ="~~~    "; //后面有空格ConStr = str1+str2+str3;trimStr = ConStr.trim();console.log(trimStr);
    

    圖 1 中可以看到 ConStr InstanceType 的值是 CONS_STRING_TYPE,它導致 “TryToDirect 失敗” 并啟動 Runtime。

     StringTrim 源碼

    源碼如下:

    1.  RUNTIME_FUNCTION(Runtime_StringTrim) {2.    HandleScope scope(isolate);3.    DCHECK_EQ(2, args.length());4.    Handle string = args.at(0);5.    CONVERT_SMI_ARG_CHECKED(mode, 1);6.    String::TrimMode trim_mode = static_cast(mode);7.    return *String::Trim(isolate, string, trim_mode);8.  }9.  //分隔線.............10.  Handle String::Trim(Isolate* isolate, Handle string,11.                              TrimMode mode) {12.    string = String::Flatten(isolate, string);13.    int const length = string->length();14.    // Perform left trimming if requested.15.    int left = 0;16.    if (mode == kTrim || mode == kTrimStart) {17.      while (left < length && IsWhiteSpaceOrLineTerminator(string->Get(left))) {18.        left++;19.      }20.    }21.    // Perform right trimming if requested.22.    int right = length;23.    if (mode == kTrim || mode == kTrimEnd) {24.      while (right > left &&25.             IsWhiteSpaceOrLineTerminator(string->Get(right - 1))) {26.        right--;27.      }28.    }29.    return isolate->factory()->NewSubString(string, left, right);30.  }
    

    上述代碼中,第 4 行代碼獲取字符串,也就是測試用例的 ConStr;

    第 6 行代碼調用 *String::Trim(isolate, string, trim_mode) 以完成 Trim 功能;

    第 12 行代碼對 ConStr 進行 Flatten 處理,結果保存為連續存儲的字符串 string。因為 ConStr 由三個子串組成,所以 Flatten 方法中會使用遞歸調用來處理 ConStr,詳見上篇文章。

    第 16-17 行代碼從 string 的頭部依次判斷每個字符是否為空格或行結尾符,記錄不是空格或行結尾符的位置 left;

    第 24-26 行代碼從 string 的尾部依次判斷每個字符是否為空格或行結尾符,記錄不是空格或行結尾符的位置 right;

    第 29 行代碼調用 NewSubString 生成新的字符串。正如 ECMA 所說的那樣:Trim 不會改變原字符串,而是生成新的字符串。

    NewSubString 中調用 NewProperSubString 以生成最終的結果,NewProperSubString 源碼分析參見上一篇文章。

    下面給出判斷空格和行結尾符的函數源碼:

    bool IsWhiteSpaceOrLineTerminator(uc32 c) {  if (!IsInRange(c, 0, 127)) return IsWhiteSpaceOrLineTerminatorSlow(c);  DCHECK_EQ(      IsWhiteSpaceOrLineTerminatorSlow(c),      static_cast<bool>(kAsciiCharFlags[c] & kIsWhiteSpaceOrLineTerminator));  return kAsciiCharFlags[c] & kIsWhiteSpaceOrLineTerminator;}
    

    首先判斷字符是否在 0-127 區間,如果不在區間內使用 Slow 方式判斷,源碼如下:

    inline bool IsWhiteSpaceOrLineTerminatorSlow(uc32 c) {  return IsWhiteSpaceSlow(c) || unibrow::IsLineTerminator(c);}//.....................分隔線................// ES#sec-white-space White Space// gC=Zs, U+0009, U+000B, U+000C, U+FEFFbool IsWhiteSpaceSlow(uc32 c) {  return (u_charType(c) == U_SPACE_SEPARATOR) ||         (c < 0x0D && (c == 0x09 || c == 0x0B || c == 0x0C)) || c == 0xFEFF;}//....................分隔線...................// LineTerminator:       'JS_Line_Terminator' in point.properties// ES#sec-line-terminators lists exactly 4 code points:// LF (U+000A), CR (U+000D), LS(U+2028), PS(U+2029)V8_INLINE bool IsLineTerminator(uchar c) {  return c == 0x000A || c == 0x000D || c == 0x2028 || c == 0x2029;}
    

    上述代碼分為三部分,第二、三部實現 ECMA 規范,第一部分是他們的入口函數。

    IsWhiteSpaceOrLineTerminator() 中的 kAsciiCharFlags 數組定義 Ascii 字符,kAsciiCharFlags 數組中又引用了 BuildAsciiCharFlags() 方法,該方法說明了 \t、\v 是空格、還是行結尾符,也就是 BuildAsciiCharFlags() 方法影響 Strint.trim() 的結果。源碼如下:

    const constexpr uint8_t kAsciiCharFlags[128] = {#define BUILD_CHAR_FLAGS(N) BuildAsciiCharFlags(N),    INT_0_TO_127_LIST(BUILD_CHAR_FLAGS)#undef BUILD_CHAR_FLAGS};//................分隔線.........................constexpr uint8_t BuildAsciiCharFlags(uc32 c) {  return ((IsAsciiIdentifier(c) || c == '\\')              ? (kIsIdentifierPart |                 (!IsDecimalDigit(c) ? kIsIdentifierStart : 0))              : 0) |         ((c == ' ' || c == '\t' || c == '\v' || c == '\f')              ? kIsWhiteSpace | kIsWhiteSpaceOrLineTerminator              : 0) |         ((c == '\r' || c == '') ? kIsWhiteSpaceOrLineTerminator : 0);}//...............分隔線.......................#define INT_0_TO_127_LIST(V)                                          \V(0)   V(1)   V(2)   V(3)   V(4)   V(5)   V(6)   V(7)   V(8)   V(9)   \V(10)  V(11)  V(12)  V(13)  V(14)  V(15)  V(16)  V(17)  V(18)  V(19)  \V(20)  V(21)  V(22)  V(23)  V(24)  V(25)  V(26)  V(27)  V(28)  V(29)  \V(30)  V(31)  V(32)  V(33)  V(34)  V(35)  V(36)  V(37)  V(38)  V(39)  \V(40)  V(41)  V(42)  V(43)  V(44)  V(45)  V(46)  V(47)  V(48)  V(49)  \V(50)  V(51)  V(52)  V(53)  V(54)  V(55)  V(56)  V(57)  V(58)  V(59)  \V(60)  V(61)  V(62)  V(63)  V(64)  V(65)  V(66)  V(67)  V(68)  V(69)  \V(70)  V(71)  V(72)  V(73)  V(74)  V(75)  V(76)  V(77)  V(78)  V(79)  \V(80)  V(81)  V(82)  V(83)  V(84)  V(85)  V(86)  V(87)  V(88)  V(89)  \V(90)  V(91)  V(92)  V(93)  V(94)  V(95)  V(96)  V(97)  V(98)  V(99)  \V(100) V(101) V(102) V(103) V(104) V(105) V(106) V(107) V(108) V(109) \V(110) V(111) V(112) V(113) V(114) V(115) V(116) V(117) V(118) V(119) \V(120) V(121) V(122) V(123) V(124) V(125) V(126) V(127)
    

    上述代碼分為三部分,他們共同完成 kAsciiCharFlags 數組的定義。

    下面給出從字符串中讀取字符的函數源碼,也就是 IsWhiteSpaceOrLineTerminator(string->Get(left)) 中的 “Get” 方法,源碼如下:

    uint16_t String::Get(int index) {  DCHECK(index >= 0 && index < length());
      class StringGetDispatcher : public AllStatic {   public:#define DEFINE_METHOD(Type)                                  \  static inline uint16_t Handle##Type(Type str, int index) { \    return str.Get(index);                                   \  }    STRING_CLASS_TYPES(DEFINE_METHOD)#undef DEFINE_METHOD    static inline uint16_t HandleInvalidString(String str, int index) {      UNREACHABLE();    }  };
      return StringShape(*this)      .DispatchToSpecificTypeuint16_t>(*this, index);}
    

    Get 方法用于讀取 index 位置的字符。從 String 中讀取字符時,要根據 String Header 的長度計算字符串的首位置,然后再加上 index 讀取相應的字符。

    技術總結

    (1) Runtime Trim 的效率比 TF_BUILTIN(StringPrototypeTrim) 低很多;

    (2) 字符串的類型影響 TryToDirect 的成敗。

    好了,今天到這里,下次見。

    個人能力有限,有不足與紕漏,歡迎批評指正

    stringruntime
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    JSP Webshell的檢測工具
    2021-12-13 12:04:53
    在11月初,我做了一些JSP Webshell的免殺研究,主要參考了三夢師傅開源的代碼。然后加入了一些代碼混淆手段,編寫了一個免殺馬生成器JSPHorse,沒想到在Github上已收獲500+的Star
    這樣就獲取了system權限,接下來就是對機器進行信息搜集,獲取對我們橫向滲透有幫助的信息。其中記錄了tomcat部署的路徑,以及備份的源碼文件。經過一番爆破,發現了一臺機器oracle數據庫system用戶密碼未修改,為manager。大家應該知道oracle數據庫是可以執行系統命令的,而system用戶完全符合執行系統命令的條件。后面發現是火絨攔截了異常行為。
    //把Map的購物車轉換為Item列表??//應付總價=商品總價+運費總價-總優惠??然后實現針對 VIP 用戶的購物車邏輯。所以,這部分代碼只需要額外處理多買折扣部分:public?
    業務同學抱怨業務開發沒有技術含量,用不到設計模式、Java 高級特性、OOP,平時寫代碼都在堆?CRUD,個人成長無從談起。其實,我認為不是這樣的。設計模式、OOP 是前輩們在大型項目中積累下來的經驗,通過這些方法論來改善大型項目的可維護性。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    漏洞分析花了蠻多時間
    獲取到類之后,我們就可以通過反射來間接調用里面的方法,獲取里面的變量等。
    java反射機制是什么
    介紹Runtime 是一系列采用 C++ 語言編寫的功能方法,它實現了大量 JavaScript 運行期間需要的 native 功能。本文分析 Runtime_StringToArray 方法的源碼和重要數據結構,講解 Runtime_StringToArray 方法的觸發條件。
    介紹Runtime 是一系列采用 C++ 語言編寫的功能方法,它實現了大量 JavaScript 運行期間需要的 native 功能。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类