<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>

    replace 技術細節、性能影響因素

    VSole2021-12-27 16:47:57

    介紹

    字符串是 JavaScript 中的重要數據類型,其重要性不僅體現在字符串是應用最多最廣泛的數據類型,更體現在V8中使用了大量的技術手段來修飾和優化字符串的操作。接下來的幾篇文章將集中講解字符串的相關操作。本文先講解 String.prototype.replace 的源碼以及相關數據結構,再通過測試用例演示 String.prototype.replace 的調用、加載和執行過程。我們會看字符串的類型、長度等因素如何影響 replace 的實現方式和效率。

    注意 (1)Sea of Nodes 是本文的先導知識,請參考 Cliff 1993年發表的論文 From Quads to Graphs。(2)本文所用環境為:V8 7.9、win10 x64、VS2019。

    String.prototype.replace 源碼

    測試用例代碼如下:

    var str="Visit Microsoft!Visit Microsoft!";var res=str.replace("Microsoft","Runoob");console.log(res);
    

    replace() 采用 TF_BUILTIN 實現,replace() 在 V8 中的函數名是 StringPrototypeReplace,編號是 594,源碼如下:

    1.  TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {2.  Label out(this);3.  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));4.  Node* const search = Parameter(Descriptor::kSearch);5.  Node* const replace = Parameter(Descriptor::kReplace);6.  TNode<Context> context = CAST(Parameter(Descriptor::kContext));7.  TNode<Smi> const smi_zero = SmiConstant(0);8.  RequireObjectCoercible(context, receiver, "String.prototype.replace");9.  MaybeCallFunctionAtSymbol(/*省略.....*/);10.   TNode<String> const subject_string = ToString_Inline(context, receiver);11.   TNode<String> const search_string = ToString_Inline(context, search);12.   TNode<IntPtrT> const subject_length = LoadStringLengthAsWord(subject_string);13.   TNode<IntPtrT> const search_length = LoadStringLengthAsWord(search_string);14.   {15.     Label next(this);16.     GotoIfNot(WordEqual(search_length, IntPtrConstant(1)), &next);17.     GotoIfNot(IntPtrGreaterThan(subject_length, IntPtrConstant(0xFF)), &next);18.     GotoIf(TaggedIsSmi(replace), &next);19.     GotoIfNot(IsString(replace), &next);20.     TNode<Uint16T> const subject_instance_type =21.         LoadInstanceType(subject_string);22.     GotoIfNot(IsConsStringInstanceType(subject_instance_type), &next);23.     GotoIf(TaggedIsPositiveSmi(IndexOfDollarChar(context, replace)), &next);24.     Return(CallRuntime(Runtime::kStringReplaceOneCharWithString, context,25.                        subject_string, search_string, replace));26.     BIND(&next); }27.   TNode<Smi> const match_start_index =28.       CAST(CallBuiltin(Builtins::kStringIndexOf, context, subject_string,29.                        search_string, smi_zero));30.   {31.     Label next(this), return_subject(this);32.     GotoIfNot(SmiIsNegative(match_start_index), &next);33.     GotoIf(TaggedIsSmi(replace), &return_subject);34.     GotoIf(IsCallableMap(LoadMap(replace)), &return_subject);35.     ToString_Inline(context, replace);36.     Goto(&return_subject);37.     BIND(&return_subject);38.     Return(subject_string);39.     BIND(&next); }40.   TNode<Smi> const match_end_index = SmiAdd(match_start_index, SmiFromIntPtr(search_length));41.   VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant());42.   {43.     Label next(this);44.     GotoIf(SmiEqual(match_start_index, smi_zero), &next);45.     TNode<Object> const prefix =46.         CallBuiltin(Builtins::kStringSubstring, context, subject_string,47.                     IntPtrConstant(0), SmiUntag(match_start_index));48.     var_result.Bind(prefix);49.     Goto(&next);50.     BIND(&next); }51.   Label if_iscallablereplace(this), if_notcallablereplace(this);52.   GotoIf(TaggedIsSmi(replace), &if_notcallablereplace);53.   Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace,54.          &if_notcallablereplace);55.   BIND(&if_iscallablereplace);56.   {57.     Callable call_callable = CodeFactory::Call(isolate());58.     Node* const replacement =59.         CallJS(call_callable, context, replace, UndefinedConstant(),60.                search_string, match_start_index, subject_string);61.     TNode<String> const replacement_string =62.         ToString_Inline(context, replacement);63.     var_result.Bind(CallBuiltin(Builtins::kStringAdd_CheckNone, context,64.                                 var_result.value(), replacement_string));65.     Goto(&out); }66.   BIND(&if_notcallablereplace);67.   {68.     TNode<String> const replace_string = ToString_Inline(context, replace);69.     Node* const replacement =70.         GetSubstitution(context, subject_string, match_start_index,71.                         match_end_index, replace_string);72.     var_result.Bind(CallBuiltin(Builtins::kStringAdd_CheckNone, context,73.                                 var_result.value(), replacement));74.     Goto(&out);}75.   BIND(&out);76.   {77.     TNode<Object> const suffix =78.         CallBuiltin(Builtins::kStringSubstring, context, subject_string,79.                     SmiUntag(match_end_index), subject_length);80.     TNode<Object> const result = CallBuiltin(81.         Builtins::kStringAdd_CheckNone, context, var_result.value(), suffix);82.      Return(result);83.    }}
    

    上述代碼第 3 行代碼 receiver 代表測試用例字符串 “Visit Microsoft!Visit Microsoft!”;

    第 4 行代碼 search 代表測試用例字符串 “Microsoft”;

    第 5 行代碼 replace 代表測試用例字符串 “Runoob”;

    第 9 行代碼當 search 為正則表達式時,使用 MaybeCallFunctionAtSymbol() 實現 replace 功能。

    下面將 replace 源碼劃分為五個子功能單獨說明:

    (1) 功能 1:receiver 長度大于 0xFF、search 長度大于 1 以及 replace 為 ConsString,這三個條件同時成立時

    第 10-13 行代碼轉換 search 和 replace 的數據類型,并計算他們的長度;

    第 16 行代碼判斷 search 的長度是否等于 1,不等于則跳轉到第 26 行;

    第 17 行代碼判斷 receiver 的長度是否大于 0xFF,小于等于則跳轉到第 26 行;

    第 18-19 行判斷 replace 是否為小整數或者 replace 不是字符串,結果為真則跳轉到第 26 行;

    第 20-22 行判斷 replace 是否為 ConsString 類型,結果為假則跳轉到第 26 行;提示 ConsString不是一個獨立的字符串,它是使用指針把兩個字符串拼接在一起的字符串對。在 V8 中,兩個字符串相加的結果常是 ConsString 類型的字符串對。

    第 23 行判斷 PositiveSmi 類型;

    第 24 行采用 Runtime::kStringReplaceOneCharWithString() 完成 replace。

    (2) 功能 2:計算 receiver 中的前綴字符串

    第 27-32 行計算 search的第一個字符在 receiver 中的位置 match_start_index,如果 match_start_index 不是負數則跳轉到 39 行;

    第 33-35 行判斷 replace 是否為 SMI 或 函數,是則跳轉到 37 行;

    第 40 行計算 match_end_index;

    第 44 行如果 match_start_index = 0(也就是 search[0]=receiver[0]),則跳轉到 49 行;

    第 45 行取出 receiver 中 match_start_index 位置之前的字符,保存為 prefix;也就是獲取測試用例中的 “Visit ” 字符串;

    第 50-54 行判斷 replace 是否為 SMI 或 函數,根據判斷結果跳轉到相應的行號。

    (3) 功能 3:replace 是函數類型

    第 58-63 行計算 replace 的結果,將該結果拼接在 prefix 后面組成新的字符串 var_result;

    (4) 功能 4:replace 是字符串類型

    第 58-72 行將 replace 拼接在 prefix 后面組成新的字符串 var_result;

    (5) 計算 receiver 中的后綴字符串

    第 77-82 行取出 receiver 中 match_end_index 之后的字符串 suffix,將 suffix 拼接在 var_result 后面組成并返回新的字符串,replace 完畢。

    下面簡單說明 replace 中使用的 runtime 方法:

    1.  RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {2.    HandleScope scope(isolate);3.    DCHECK_EQ(3, args.length());4.    CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);5.    CONVERT_ARG_HANDLE_CHECKED(String, search, 1);6.    CONVERT_ARG_HANDLE_CHECKED(String, replace, 2);7.    const int kRecursionLimit = 0x1000;8.    bool found = false;9.    Handle<String> result;10.    if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,11.                                       kRecursionLimit).ToHandle(&result)) {12.      return *result;13.    }14.    if (isolate->has_pending_exception())15.      return ReadOnlyRoots(isolate).exception();16.    subject = String::Flatten(isolate, subject);17.    if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,18.                                       kRecursionLimit).ToHandle(&result)) {19.      return *result;20.    }21.    if (isolate->has_pending_exception())22.      return ReadOnlyRoots(isolate).exception();23.    return isolate->StackOverflow();24.  }
    

    上述代碼中第 10 執行 StringReplaceOneCharWithString 方法,該方法實現了 replace 功能;

    第 14 行代碼檢測到異常情況時,先執行 String::Flatten,將ConsString字符處理成為單一字符串之后再次執行 StringReplaceOneCharWithString 方法。

    圖1給出 StringPrototypeReplace 的函數調用堆棧。

    String.prototype.replace 測試

    測試用例的字節碼如下:

    1.  //省略........2.  000001A2EE982AC6 @   16 : 12 01             LdaConstant [1]3.  000001A2EE982AC8 @   18 : 15 02 04          StaGlobal [2], [4]4.  000001A2EE982ACB @   21 : 13 02 00          LdaGlobal [2], [0]5.  000001A2EE982ACE @   24 : 26 f9             Star r26.  000001A2EE982AD0 @   26 : 29 f9 03          LdaNamedPropertyNoFeedback r2, [3]7.  000001A2EE982AD3 @   29 : 26 fa             Star r18.  000001A2EE982AD5 @   31 : 12 04             LdaConstant [4]9.  000001A2EE982AD7 @   33 : 26 f8             Star r310.  000001A2EE982AD9 @   35 : 12 05             LdaConstant [5]11.  000001A2EE982ADB @   37 : 26 f7             Star r412.  000001A2EE982ADD @   39 : 5f fa f9 03       CallNoFeedback r1, r2-r413.  000001A2EE982AE1 @   43 : 15 06 06          StaGlobal [6], [6]14.  000001A2EE982AE4 @   46 : 13 07 08          LdaGlobal [7], [8]15.  000001A2EE982AE7 @   49 : 26 f9             Star r216.  000001A2EE982AE9 @   51 : 29 f9 08          LdaNamedPropertyNoFeedback r2, [8]17.  000001A2EE982AEC @   54 : 26 fa             Star r118.  000001A2EE982AEE @   56 : 13 06 02          LdaGlobal [6], [2]19.  000001A2EE982AF1 @   59 : 26 f8             Star r320.  000001A2EE982AF3 @   61 : 5f fa f9 02       CallNoFeedback r1, r2-r321.  000001A2EE982AF7 @   65 : 26 fb             Star r022.  000001A2EE982AF9 @   67 : ab                Return23.  Constant pool (size = 9)24.  000001A2EE982A29: [FixedArray] in OldSpace25.   - map: 0x022d5e100169 <Map>26.   - length: 927.  0: 0x01a2ee9829c9 <FixedArray[8]>28.  1: 0x01a2ee9828c1 <String[#32]: Visit Microsoft!Visit Microsoft!>29.  2: 0x01a2ee9828a9 <String[#3]: str>30.  3: 0x02749a2ab821 <String[#7]: replace>31.  4: 0x01a2ee982909 <String[#9]: Microsoft>32.  5: 0x01a2ee982929 <String[#6]: Runoob>33.  6: 0x01a2ee9828f1 <String[#3]: res>34.  7: 0x02749a2b3699 <String[#7]: console>35.  8: 0x02749a2b2cd9 <String[#3]: log>
    

    上述代碼中,第 2-5 行讀取字符串 “Visit Microsoft!Visit Microsoft!” 并保存到 r2 寄存器中;

    第 6-7 行獲取 replace 函數地址并保存到 r1 寄存器中;

    第 8-11 行讀取 “Microsoft” 和 “Runoob” 并分別保存到 r3 和 r4 寄存器中;

    第 12 行調用 repalce 方法;

    圖 2 給出了 CallNoFeedback 的調用堆棧。

    技術總結

    (1) receiver 長度大于 0xFF、search 長度大于 1 以及 replace為ConsString,三個條件同時成立時采用低效率的 runtime 方式處理;

    (2) replace 的原理是計算前、后綴,并與 newvalue 拼接組成最終結果。

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

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

    stringreplace
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    協議分析實戰
    2022-08-18 16:56:24
    協議分析是逆向技術中的一個重要技能,本篇文章先分享3個app。這里我打算搜索post請求中的v2/member,和"system_name"。然后看這個device_id就是表示手機的串號:是這樣聲明的:就是返回手機的型號,沒有什么好說的。然后下一個就是本機IP地址,還有時間timestamp,siteid站點標識符定值10001,系統名稱system_name,型號type是Android的。這樣的話第一個app的協議字段到此就分析完了。第二個app:我輸入的用戶名是kanxue,密碼是kanxue123。
    簡介最近做一個web探測的項目,在項目中多處用到java正則表達式,今天將回顧的正則表達式做一個梳理,本文屬于看完廖大佬筆記后的梳理,僅為平時參考,詳細大家可以查看下面相關網址。
    字符串是 JavaScript 中的重要數據類型
    留神,變量名和等號之間不能有空格,這或許和你了解的悉數編程言語都不相同。一同,變量名的命名須遵從如下規矩:命名只能運用英文字母,數字和下劃線,首個字符不能以數字開端。不能運用bash里的要害字。變量稱謂一般習氣為大寫有用的 Shell 變量名示例如下:RUNOOB. 事實上,shell腳本就是一些指令的集結。
    1 介紹XSS 是網絡中最常見的漏洞,再配合其它的攻擊手段往往能輕易敲開服務器的大門。在各大漏洞平臺中,XSS 漏洞也是數量最多的。傳播記錄描述了數據從 source 到 sink 的變化過程,傳播記錄的完整性直接影響 XSS 的檢測結果。眾所周知,觸發 XSS 漏洞的字符串往往是來自于不同 source 的字符的組合,如果不能完整記錄數據變化的過程或做不到字符級別的描述,那將會產生大量的漏報。做好以下這些 API 的記錄就可以最大程度保證傳播記錄的完整性。
    使用java和redis實現一個簡單的熱搜功能,具備以下功能: 搜索欄展示當前登陸的個人用戶的搜索歷史記錄,刪除個人歷史記錄 用戶在搜索欄輸入某字符,則將該字符記錄下來 以zset格式存儲的redis中,記錄該字符被搜索的個數以及當前的時間戳 (用了DFA算法,感興趣的自己百度學習吧) 每當用戶查詢了已在redis存在了的字符時,則直接累加個數, 用來獲取平臺上最熱查詢的十條數據。(可以自己寫接
    從fofa中搜索RDP,會看到它會解析出RDP的信息。本文探索如何自己實現一個。1. Nmap指紋在http
    python encdecshellcode.py --shellcode \x41\x41\x42\x42 --key SECRETKEY --option encrypt
    m4ngl3m3是一款功能強大的常見密碼模式生成工具,該工具可以幫助廣大研究人員使用字符串列表來生成常見的密碼模式。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类