精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

關(guān)于C++內(nèi)存問(wèn)題排查攻略

開(kāi)發(fā)
雖然使用現(xiàn)代C++能夠有效解決大部分問(wèn)題,但掌握常用的內(nèi)存問(wèn)題排查方法仍然十分必要,特別是在維護(hù)一些歷史系統(tǒng)時(shí)。

作者 | johncchen

C++因其高性能仍然是許多關(guān)鍵應(yīng)用的首選語(yǔ)言,但其復(fù)雜的內(nèi)存管理也帶來(lái)了諸多挑戰(zhàn)。雖然使用現(xiàn)代C++能夠有效解決大部分問(wèn)題,但掌握常用的內(nèi)存問(wèn)題排查方法仍然十分必要,特別是在維護(hù)一些歷史系統(tǒng)時(shí)。本文分為上下兩篇:上篇(15)按照問(wèn)題分類(lèi)介紹和比較常用工具,下篇(67)通過(guò)兩個(gè)具體案例展示這些工具的組合使用,希望能為讀者帶來(lái)有益的啟發(fā)。筆者個(gè)人水平有限,文中難免存在疏漏之處,歡迎大家批評(píng)指正。

一、棧溢出(stack-overflow):查看coredump文件為主,動(dòng)態(tài)檢測(cè)為輔

棧溢出的定位方法主要有靜態(tài)分析、動(dòng)態(tài)檢測(cè)、查看coredump文件三種。

1. 靜態(tài)分析

(1) 原理

GCC提供了-fstack-usage選項(xiàng),能輸出每個(gè)函數(shù)棧的最大使用量。開(kāi)啟后,為每個(gè)編譯目標(biāo)創(chuàng)建.su文件,每行包括函數(shù)名、字節(jié)數(shù)、修飾符(static/dynamic/bounded)中的一個(gè)或多個(gè)。修飾符的含義如下:

  • static: 堆棧使用量在編譯時(shí)是已知的,不依賴(lài)于任何運(yùn)行時(shí)條件。
  • dynamic: 堆棧使用量依賴(lài)于運(yùn)行時(shí)條件,例如遞歸調(diào)用或基于輸入數(shù)據(jù)的條件分支。
  • bounded: 堆棧使用量雖然依賴(lài)于運(yùn)行時(shí)條件,但有一個(gè)可預(yù)知的上限。

(2) 舉個(gè)栗子

void static_stack_usage() { int static_array[5]; }
void dynamic_stack_usage(int n) { int val[n]; }
int main() {
  static_stack_usage();
  int n = 10;
  dynamic_stack_usage(n);
  return 0;
}

g++ ./stack_test.cc -o stack_test -fstack-usage 

./stack_test.cc:2:6:void static_stack_usage() 16 static
./stack_test.cc:4:6:void dynamic_stack_usage(int) 48 dynamic
./stack_test.cc:6:5:int main() 32 static

疑問(wèn):看到這里,估計(jì)有小伙伴會(huì)問(wèn)了:既然dynamic是不確定的,靜態(tài)分析還有意義嗎?其實(shí),實(shí)際代碼的.su一般是下面這種,dynamic和bounded組合在一起,雖然動(dòng)態(tài)但有上限,因此可以計(jì)算出“最大”的棧用量。

xxbuild.cpp:277:5:int XXBuild::BuildPage() 528 dynamic,bounded

每個(gè)函數(shù)的棧使用量有了,如果知道函數(shù)的調(diào)用鏈就可以得出棧的最大使用量了。調(diào)用鏈可以從二進(jìn)制文件中反匯編得到。

(3) 工具

靜態(tài)分析常用于資源有限的嵌入式系統(tǒng),常常集成在它們的開(kāi)發(fā)工具中。但非嵌入式系統(tǒng)的這類(lèi)工具比較少。開(kāi)源的有 checkStackUsage等,收費(fèi)的有stackanalyzer等。

注意事項(xiàng):

若使用bazel編譯,默認(rèn)的沙箱模式會(huì)刪除.su文件,因此編譯時(shí)需要增加--spawn_strategy=standalone選項(xiàng)(非沙箱模式)

2. 動(dòng)態(tài)檢測(cè)

(1) 通過(guò)proc文件系統(tǒng)

pmap或查看/proc/pid/maps中的stack,缺點(diǎn)是進(jìn)程退出后就看不到了。

(2) 捕捉操作系統(tǒng)信號(hào)

原理:

  • 在 Unix-like 系統(tǒng)中,當(dāng)程序執(zhí)行非法內(nèi)存訪問(wèn)時(shí),操作系統(tǒng)會(huì)向該程序發(fā)送 SIGSEGV 信號(hào)(段錯(cuò)誤)。默認(rèn)情況下,接收到此信號(hào)的程序會(huì)終止。
  • 如果通過(guò)注冊(cè)一個(gè)自定義的信號(hào)處理函數(shù)來(lái)攔截 SIGSEGV信號(hào),處理函數(shù)會(huì)收到一個(gè) siginfo_t 結(jié)構(gòu)體,其中包含錯(cuò)誤的地址和寄存器狀態(tài)等上下文信息,可以判斷是否發(fā)生了棧溢出。

工具:

libsigsegv-devel,可以定義自己的處理函數(shù)來(lái)響應(yīng)內(nèi)存訪問(wèn)錯(cuò)誤,例如嘗試恢復(fù)、記錄錯(cuò)誤信息或者優(yōu)雅地關(guān)閉程序。

注意事項(xiàng):

libsigsegv是GPL協(xié)議

3. 查看coredump文件

重點(diǎn)關(guān)注:

  • 層級(jí)是否過(guò)多,是否遞歸調(diào)用
  • 棧變量是否過(guò)大

修改棧(以及線(xiàn)程堆棧、協(xié)程堆棧)大小后測(cè)試。

二、棧緩沖區(qū)溢出(stack-buffer-overflow):GCC -fstack-protector/C11 Annex K/AddressSanitizer

棧緩沖區(qū)溢出原因中很大一部分是數(shù)組索引/指針越界。在我看來(lái),在項(xiàng)目中停止使用C風(fēng)格的指針、使用STL容器能解決大部分問(wèn)題。當(dāng)然,一些項(xiàng)目處于維護(hù)狀態(tài),大規(guī)模改造未必合算,可以考慮使用以下工具。

1. GCC -fstack-protector

-fstack-protector的原理:

  • 函數(shù)調(diào)用時(shí),編譯器在棧上分配一個(gè)隨機(jī)生成的 canary 值(guard值),通常被放置在局部變量和控制數(shù)據(jù)(如返回地址)之間。
  • 函數(shù)執(zhí)行過(guò)程中,所有的局部變量操作都應(yīng)當(dāng)保持 canary 值不變。如果有緩沖區(qū)溢出,超出局部變量的數(shù)據(jù)可能會(huì)覆蓋到 canary 值。
  • 如果 canary 值被修改,程序會(huì)認(rèn)為發(fā)生了棧溢出攻擊,通常會(huì)立即終止,例如通過(guò)調(diào)用 __stack_chk_fail() 函數(shù)。

有不同的保護(hù)強(qiáng)度-fstack-protector/-fstack-protector-all/-fstack-protector-strong/-fstack-protector-explicit,一般-fstack-protector-strong即可。

2. C11 Annex K (Bounds-checking interfaces)

使用 C11 標(biāo)準(zhǔn)中引入的strncpy_s()等函數(shù),比 strcpy()/strncpy() 等函數(shù)更安全。它要求指定源和目標(biāo)的大小,并在復(fù)制過(guò)程中檢查這些大小,以防止溢出。如果發(fā)生錯(cuò)誤(如無(wú)效參數(shù)或目標(biāo)太小),strncpy_s() 將設(shè)置 errno 并可以選擇使程序失敗。

較低版本的gcc不支持c11, 可以使用一些第三方實(shí)現(xiàn),比如的openharmony的third_party_bounds_checking_function

3.  AddressSanitizer

詳見(jiàn)4.1

4. Valgrind memcheck

詳見(jiàn)4.2

三、內(nèi)存泄漏:eBPF+火焰圖,高效直觀

1.Valgrind memcheck/AddressSanitizer/eBPF bcc-tools memleak比較

eBPF的最大的優(yōu)點(diǎn)是“非侵入”,不需要重新編譯或重啟業(yè)務(wù)進(jìn)程,對(duì)運(yùn)行速度和內(nèi)存用量的影響極小,可以忽略不計(jì),可以線(xiàn)上使用。

2. eBPF bcc-tools memleak檢測(cè)原理

eBPF程序是事件驅(qū)動(dòng)的,在內(nèi)核或應(yīng)用經(jīng)過(guò)特定鉤子點(diǎn)(hook point)時(shí)運(yùn)行。在memleak的源碼中可以看到注冊(cè)到了以下鉤子點(diǎn)

attach_probes("malloc")
attach_probes("calloc")
attach_probes("realloc")
attach_probes("mmap", can_fail=True)  # failed on jemalloc
attach_probes("posix_memalign")
attach_probes("valloc", can_fail=True)  # failed on Android, is deprecated in libc.so from bionic directory
attach_probes("memalign")
attach_probes("pvalloc", can_fail=True)  # failed on Android, is deprecated in libc.so from bionic directory
attach_probes("aligned_alloc", can_fail=True)  # added in C11
attach_probes("free", need_uretprobe=False)
attach_probes("munmap", can_fail=True, need_uretprobe=False)  # failed on jemalloc

3. 舉個(gè)栗子

先寫(xiě)一段內(nèi)存泄漏(不斷增長(zhǎng))的測(cè)試代碼

#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
#include <string>

void LeakOnce(std::vector<std::string>& strs) {
    // Generate a random string
    std::string str;
    const std::string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for (int i = 0; i < 10; i++) {
        char randomChar = characters[rand() % characters.length()];
        str += randomChar;
    }
    strs.emplace_back(std::move(str));
}

void CallLeak(){
    std::vector<std::string> strs;
    while(true){
        LeakOnce(strs);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    CallLeak();
    return 0;
}

g++ ./leak_test.cc -o leak_test --std=c++11 -g

檢測(cè)結(jié)果如圖,符合預(yù)期~

memleak具體選項(xiàng)詳見(jiàn)-h,也可以參考官方例子。需要注意的是-O選項(xiàng), attach to allocator functions in the specified object. 如果沒(méi)有使用glibc而是使用jemlloc或tcmalloc,需要使用-O指定二進(jìn)制文件(靜態(tài)鏈接)或動(dòng)態(tài)庫(kù)(動(dòng)態(tài)鏈接)。

4.  改進(jìn)memleak,支持火焰圖

實(shí)際的內(nèi)存泄漏經(jīng)常是小規(guī)模、長(zhǎng)時(shí)間的,會(huì)混雜在大量正常的內(nèi)存申請(qǐng)和釋放動(dòng)作中,這時(shí)候memleak文本形式的輸出就不夠直觀了。想到cpu性能調(diào)優(yōu)經(jīng)常用到的火焰圖,如果memleak能生成直觀的火焰圖就好了。

火焰圖的格式并不復(fù)雜,格式如下:

[堆棧] [采樣值]
main;foo;bar 76

PR4766有一個(gè)繪制火焰圖的簡(jiǎn)單實(shí)現(xiàn),沒(méi)有合入主干很可惜。可以參考它,來(lái)修改已安裝的bcc/tools/memleak。修改后執(zhí)行:

/usr/share/bcc/tools/memleak2.py -p $(pgrep leak_test)  --report --report-file leak_test.stacks
flamegraph.pl --color=mem  --countname="bytes"< leak_test.stacks > leak_test.svg

在中大型項(xiàng)目中,火焰圖能夠很好地區(qū)分框架與業(yè)務(wù)模塊的內(nèi)存操作,便于逐級(jí)排查,非常清晰。

四、其他內(nèi)存問(wèn)題:AddressSanitizer為主,Valgrind memcheck為輔

1. AddressSanitizer

編譯和鏈接時(shí)加上-fsanitize=address,完整選項(xiàng)見(jiàn)AddressSanitizerFlags,一些常用選項(xiàng)如下:

export :ASAN_OPTIONS="log_path=/my_path/asan:abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1:debug=true:check_initialization_order=true:print_stats=true:strict_string_checks=true:dump_instruction_bytes=true"

AddressSanitizer會(huì)使程序運(yùn)行慢約2倍,比Valgrind memcheck好太多,可以考慮使用線(xiàn)上節(jié)點(diǎn)排查問(wèn)題。

2. Valgrind memcheck

運(yùn)行速度慢10~50倍,消耗大量?jī)?nèi)存,可以通過(guò)關(guān)閉檢查項(xiàng)目來(lái)提高速度、減少內(nèi)存使用。

五、多線(xiàn)程/協(xié)程的數(shù)據(jù)競(jìng)爭(zhēng)(data race):ThreadSanitizer/Valgrind的helgrind和drd基本不可用,AddressSanitizer仍然可用

1. ThreadSanitizer

編譯和鏈接增加-fsanitize=thread,編譯通常遇到std::atomic_thread_fence報(bào)錯(cuò),官方解釋如下,好吧,std::atomic_thread_fence很常見(jiàn),ThreadSanitizer基本不可用了。

-Wno-tsan Disable warnings about unsupported features in ThreadSanitizer. ThreadSanitizer does not support std::atomic_thread_fence and can report false positives.

除此之外,開(kāi)啟ThreadSanitizer對(duì)運(yùn)行速度和內(nèi)存消耗也有較大影響:

The cost of race detection varies by program, but for a typical program, memory usage may increase by 5-10x and execution time by 2-20x.

2. Valgrind helgrind/drd

比起ThreadSanitizer,需要消耗更多內(nèi)存。我做了個(gè)測(cè)試,一個(gè)使用內(nèi)存2.5G的服務(wù),使用Valgrind helgrind或drd啟動(dòng),32G內(nèi)存都不夠、直接OOM,因此在規(guī)模大些的項(xiàng)目中基本不可用。

3. AddressSanitizer仍然可用

AddressSanitizer不針對(duì)data race,但能檢測(cè)內(nèi)存異常。

下篇以排查某A服務(wù)內(nèi)存問(wèn)題的過(guò)程為例,演示上篇中工具的使用。其實(shí),上篇的工具是下篇踩坑、填坑的經(jīng)驗(yàn)總結(jié)。

六、低成本解決歷史代碼崩潰問(wèn)題

A 服務(wù)中有一大塊老舊的業(yè)務(wù)邏輯,稱(chēng)之為模塊 B,其特點(diǎn)如下:

  • 代碼行數(shù)多, 2w+
  • 大量 C 風(fēng)格字符串操作(如 strcpy 等),存在越界風(fēng)險(xiǎn)
  • 依賴(lài)大量老舊版本的第三方庫(kù)
  • 需求很少,處于維護(hù)狀態(tài)

問(wèn)題出現(xiàn):服務(wù)以前運(yùn)行平穩(wěn),但從某天開(kāi)始,線(xiàn)上節(jié)點(diǎn)隔三差五就會(huì)出現(xiàn)崩潰。查看 coredump 文件,發(fā)現(xiàn)崩潰在模塊B的代碼中, frame 0 中某些局部變量損壞。然而,重放崩潰前后一段時(shí)間內(nèi)的請(qǐng)求并不能復(fù)現(xiàn)崩潰,應(yīng)該是其他請(qǐng)求的棧緩沖區(qū)溢出,破壞了這條請(qǐng)求的棧。此類(lèi)問(wèn)題很難直接根據(jù) coredump 文件定位。

排查過(guò)程:如 2.1 中所述,使用 -fstack-protector-strong 重新編譯并上線(xiàn),結(jié)果斷斷續(xù)續(xù)地因?yàn)?nbsp;__stack_chk_fail 出現(xiàn)崩潰,這就好辦了。按圖索驥,發(fā)現(xiàn)是某些請(qǐng)求觸發(fā)了歷史 bug,導(dǎo)致一些局部變量指針越界,針對(duì)性地添加邊界判斷就修復(fù)了,從而以較小的代價(jià)解決了復(fù)雜歷史代碼的崩潰問(wèn)題。

后續(xù)措施:考慮到模塊 B 可能還有其他坑,一旦出現(xiàn)問(wèn)題將導(dǎo)致 A 服務(wù)的節(jié)點(diǎn)崩潰,影響整體 SLA。因此將模塊 B 拆分成獨(dú)立的微服務(wù) C。如果服務(wù) A 調(diào)用服務(wù) C 失敗,可以走降級(jí)鏈路,從而提高業(yè)務(wù)整體的可用性。

七、解決偶發(fā)崩潰問(wèn)題

問(wèn)題出現(xiàn):A 服務(wù)頻繁上線(xiàn),經(jīng)常在一周內(nèi)發(fā)布三四個(gè)版本。某段時(shí)間內(nèi),崩潰的概率顯著增加。查看 coredump 文件,發(fā)現(xiàn)經(jīng)常崩潰在 STL 容器(如 std::map、std::unordered_map、std::vector 等)中 std::allocator 的析構(gòu)相關(guān)函數(shù),但backstrace不確定,有時(shí)在這個(gè)模塊中有時(shí)在那個(gè)模塊中。重放崩潰前后一段時(shí)間內(nèi)的請(qǐng)求無(wú)法復(fù)現(xiàn)崩潰,推測(cè)又是內(nèi)存踩踏問(wèn)題。

第一次嘗試:逐一使用2.1 ~2.3的 GCC -fstack-protector /C11 Annex K/AddressSanitizer ,回放線(xiàn)上請(qǐng)求,結(jié)果都正常,這就尷尬了……

鑒于一時(shí)難以解決問(wèn)題,首先采取措施確保線(xiàn)上穩(wěn)定:

將容器的健康檢查方式從 TCP 改為 HTTP,這樣在 core dump 開(kāi)始而不是結(jié)束后就能檢測(cè)出節(jié)點(diǎn)異常(core 文件約 20G,core dump 過(guò)程持續(xù)幾分鐘),盡早從北極星(服務(wù)注冊(cè)與發(fā)現(xiàn)平臺(tái))上摘除,減少對(duì)線(xiàn)上的影響。這樣線(xiàn)上可以繼續(xù)開(kāi)啟coredump,方便排查問(wèn)題。

第二次嘗試:

通過(guò)監(jiān)控逐漸發(fā)現(xiàn)一些規(guī)律:崩潰集中在進(jìn)程啟動(dòng)階段,日常運(yùn)行時(shí)很少。因此懷疑與進(jìn)程啟動(dòng)時(shí)的狀態(tài)或特定請(qǐng)求有關(guān)。

下一步是復(fù)現(xiàn)問(wèn)題。在崩潰概率最高的地域,新建一個(gè)旁路 workload(兩個(gè)節(jié)點(diǎn)),將北極星權(quán)重調(diào)為其他節(jié)點(diǎn)的 1/N,使用 API 定期重啟旁路 workload 的 pod。經(jīng)過(guò)幾天,問(wèn)題復(fù)現(xiàn)了!

backstrace與之前類(lèi)似,找不出線(xiàn)索。那就上工具吧,能在線(xiàn)上使用的檢測(cè)工具也就只有 AddressSanitizer了,編譯一版部署到旁路 workload,繼續(xù)定期重啟,等待結(jié)果……

果然,斷斷續(xù)續(xù)出現(xiàn)了一些崩潰,但查看 coredump 文件的backstrace仍難以找到有效線(xiàn)索。有時(shí)崩潰在插件中,有時(shí)在 encode 過(guò)程中。咨詢(xún)相關(guān)插件的同學(xué),他們也感到很奇怪,沒(méi)有思路。直到,直到,下面這個(gè)錯(cuò)誤出現(xiàn):

==181==ERROR: AddressSanitizer: attempting double-free on 0x61b000258480 in thread T14 (FiberWorker_02):
    #0 0x7f3a1f52a878 in operator delete(void*, unsigned long) ../../../../libsanitizer/asan/asan_new_delete.cpp:164
    #1 0x13d4f0c in std::__new_allocator<char>::deallocate(char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/new_allocator.h:158
    #2 0x13d4f0c in std::allocator<char>::deallocate(char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/allocator.h:200
    #3 0x13d4f0c in std::allocator_traits<std::allocator<char> >::deallocate(std::allocator<char>&, char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/alloc_traits.h:496
    #4 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_destroy(unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:300
    #5 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose() /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:294
    #6 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.tcc:338
    #7 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.tcc:420
    #8 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1430
    #9 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1396
    #10 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator+=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1338
    #11 0x1b91ac5 in construct_xx_query(thread_data*) xx/yy/zz/aa_util.cc:66
···
0x61b000258480 is located 0 bytes inside of 1539-byte region [0x61b000258480,0x61b000258a83)
freed by thread T13 (FiberWorker_01) here:
    #0 0x7f3a1f52a878 in operator delete(void*, unsigned long) ../../../../libsanitizer/asan/asan_new_delete.cpp:164
    #1 0x13d4f0c in std::__new_allocator<char>::deallocate(char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/new_allocator.h:158
    #2 0x13d4f0c in std::allocator<char>::deallocate(char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/allocator.h:200
    #3 0x13d4f0c in std::allocator_traits<std::allocator<char> >::deallocate(std::allocator<char>&, char*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/alloc_traits.h:496
    #4 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_destroy(unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:300
    #5 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose() /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:294
    #6 0x13d4f0c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.tcc:338
    #7 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.tcc:420
    #8 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(char const*, unsigned long) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1430
    #9 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1396
    #10 0x1b91ac5 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator+=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /usr/lib64/gcc/x86_64-pc-linux-gnu/12.3.0/../../../../include/c++/12.3.0/bits/basic_string.h:1338
    #11 0x1b91ac5 in construct_xx_query(thread_data*) xx/yy/zz/aa_util.cc:66
··· 

construct_xx_query(thread_data*) xx/yy/zz/aa_util.cc:66的代碼是
thread_data->string_bb += judge_cc()

查看代碼上下文,終于找到了原因!在某類(lèi)請(qǐng)求中使用協(xié)程并發(fā)調(diào)用后端服務(wù),而 thread_data->string_bb(std::string 類(lèi)型)變量是唯一的,多個(gè)協(xié)程同時(shí)修改 thread_data->string_bb,導(dǎo)致 double-free!由于同時(shí)寫(xiě)入是小概率事件,所以崩潰是偶發(fā)的。原來(lái)是 data race 問(wèn)題……

再查看提交歷史,發(fā)現(xiàn)多協(xié)程并發(fā)調(diào)用是在某個(gè)版本上線(xiàn)的,當(dāng)時(shí)一切正常;上百個(gè)版本之后,調(diào)用流程中增加了這行問(wèn)題代碼。冗長(zhǎng)膨脹的流程函數(shù)中新增一行代碼很難引起注意,多人開(kāi)發(fā)非常容易踩坑。

徹底解決問(wèn)題需要從設(shè)計(jì)入手:重構(gòu)流程,遵循單一職責(zé),將修改集中到一處,便于檢查;傳參變成只讀引用,消除 data race。

測(cè)試通過(guò),上線(xiàn),不再崩潰!

總結(jié)

大部分問(wèn)題,尤其是難以排查的問(wèn)題,應(yīng)該在設(shè)計(jì)階段就被解決掉,越往后代價(jià)越大。正所謂“善戰(zhàn)者無(wú)赫赫之功”。

責(zé)任編輯:趙寧寧 來(lái)源: 騰訊技術(shù)工程
相關(guān)推薦

2020-11-02 09:48:35

C++泄漏代碼

2010-01-21 17:30:12

C++復(fù)雜

2009-08-28 10:14:45

C#內(nèi)存泄露

2018-10-15 10:52:24

C++內(nèi)存程序員

2021-06-28 08:00:00

Python開(kāi)發(fā)編程語(yǔ)言

2015-07-20 10:23:24

NET內(nèi)存問(wèn)題排查

2018-11-06 12:12:00

MySQL內(nèi)存排查

2025-08-13 01:00:00

2014-05-29 10:54:20

C++構(gòu)造函數(shù)

2024-03-11 06:05:00

C++字符串

2011-04-11 09:47:50

C++內(nèi)存管理

2011-07-15 01:10:13

C++內(nèi)存分配

2018-08-10 15:00:42

服務(wù)器內(nèi)存排查

2022-07-03 20:31:59

JVMJava虛擬機(jī)

2024-01-25 16:19:27

2010-08-18 09:52:25

Memcache

2021-07-30 20:59:21

MySQL內(nèi)存.參數(shù)

2025-06-26 05:00:00

2011-06-21 11:16:24

cc++

2011-04-13 16:50:54

CC++內(nèi)存
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美日韩视频| 精品福利在线| 久久―日本道色综合久久| 国产精品成人va在线观看| 男人av资源站| 麻豆一区二区| 欧美美女一区二区| 秋霞无码一区二区| 在线看黄色av| 成人深夜在线观看| 国产精品一区二区三区在线播放| 加勒比av在线播放| 国产探花一区二区| 亚洲成人免费在线视频| 国产野外作爱视频播放| 暧暧视频在线免费观看| 国产精品进线69影院| 国产精品区一区二区三在线播放| 成人毛片一区二区三区| 欧美久久一区| 视频一区视频二区国产精品| 久久性爱视频网站| 96sao精品免费视频观看| 狠狠躁天天躁日日躁欧美| 日本道在线视频| 国产对白叫床清晰在线播放| 不卡电影一区二区三区| 91影院在线免费观看视频| 亚洲中文字幕无码爆乳av| 狠狠色综合网| 久久精品最新地址| 国内精品卡一卡二卡三| 一本色道久久综合亚洲精品酒店 | 国产精品丝袜久久久久久消防器材| av网站在线播放| 久久综合久久综合亚洲| 国产精品二区在线观看| 国产成人a人亚洲精品无码| 日韩国产欧美在线播放| 琪琪亚洲精品午夜在线| 日产精品久久久久| 国产精品videossex久久发布| 久久精品91久久久久久再现| 任你操精品视频| 国产欧美高清视频在线| 亚洲乱码av中文一区二区| 色哟哟视频在线| 伊人久久亚洲| 欧美成人艳星乳罩| 四虎1515hh.com| 国产精品一区二区美女视频免费看 | 伊人av成人| 国产黄色免费在线观看| 国产亚洲制服色| 欧美在线视频二区| 大乳在线免费观看| 国产日韩欧美制服另类| 日本视频一区二区不卡| av片在线看| 国产精品久久久久一区| 男人的天堂成人| 1stkiss在线漫画| 亚洲永久免费视频| 国产在线播放观看| 亚洲小少妇裸体bbw| 色视频欧美一区二区三区| 欧美激情国产精品日韩| 日韩三区免费| 欧美日韩一区二区三区高清| 91国内在线播放| 日本免费一区二区视频| 亚洲第一网站免费视频| 久久久久久久久久久国产精品| 综合色就爱涩涩涩综合婷婷| 亚洲欧洲国产伦综合| 四虎成人免费影院| 午夜精品视频一区二区三区在线看| 久久精品视频在线| 国产在线拍揄自揄拍| 亚洲精品日韩久久| 国产精品国产亚洲伊人久久| 一级特黄色大片| 成人黄色在线网站| 日韩欧美三级一区二区| 韩国av网站在线| 亚洲v中文字幕| 男人的天堂日韩| 国产视频一区二| 亚洲精品久久久久中文字幕二区 | 亚洲国产精品一| 久久久青草青青国产亚洲免观| 天堂资源在线亚洲资源| 亚洲电影视频在线| 日韩欧美精品在线观看| 久久久久久久久久一区| 国产日韩三级| 日韩在线视频观看| 久久精品视频国产| 久久爱另类一区二区小说| 国产精品久久九九| 98在线视频| 五月激情六月综合| 肉色超薄丝袜脚交| 精品国产美女| 韩国视频理论视频久久| 欧美亚洲另类小说| 国产成人日日夜夜| 亚洲一区二区三区四区中文| 97在线超碰| 男女性色大片免费观看一区二区| 色偷偷久久人人79超碰人人澡| 国产淫片av片久久久久久| 日本一区二区三区视频在线看| 亚洲人成电影在线| 日本三级视频在线| 久久se这里有精品| 免费成人看片网址| 久草在线资源站资源站| 欧美剧在线免费观看网站| 草草影院第一页| 激情亚洲成人| 91国产在线免费观看| 色综合久久影院| 色偷偷久久一区二区三区| 亚洲一级av无码毛片精品| 在线精品视频在线观看高清| 国产精品日日做人人爱| 日本亚洲一区| 午夜精品久久久久久久久久 | 成人免费网站入口| 国产在线不卡一区二区三区| 一区二区三区动漫| 国产精品自拍99| 成人av资源在线观看| 中国女人做爰视频| 国产成年精品| 日韩一级黄色av| 中文字幕91爱爱| 久久久久久免费网| 能在线观看的av| 香蕉久久夜色精品国产使用方法 | 中文字幕亚洲综合久久| 91丝袜一区二区三区| 2019国产精品| 欧美日韩一区二区在线免费观看| 网友自拍区视频精品| 91爱爱小视频k| 亚洲三级中文字幕| 精品欧美一区二区三区| 少妇户外露出[11p]| 午夜在线视频一区二区区别| 裸模一区二区三区免费| 一区二区三区四区日本视频| 日韩国产精品亚洲а∨天堂免| 国产对白videos麻豆高潮| 不卡一区二区在线| 91精品91久久久中77777老牛| 欧美性生活一级片| 国产99久久精品一区二区 夜夜躁日日躁 | 欧美特黄aaaaaaaa大片| 亚洲午夜久久久影院| 波多野结衣视频免费观看| 欧美激情在线观看视频免费| 黑森林精品导航| 五月久久久综合一区二区小说| 成人动漫网站在线观看| 天堂8中文在线| 亚洲国产欧美一区二区丝袜黑人| 中文字幕一区在线播放| av资源中文在线| 欧美自拍丝袜亚洲| 日本精品在线免费观看| 国产91丝袜在线播放| www国产精品内射老熟女| 欧美美乳视频| 成人免费直播live| 都市激情国产精品| 一区二区日韩精品| 精品人妻少妇AV无码专区| 午夜精品免费在线观看| 人妻av无码一区二区三区| 麻豆精品一区二区综合av| 337p亚洲精品色噜噜狠狠p| 牛牛精品成人免费视频| 国产精品美女www爽爽爽视频| 综合久久2019| 亚洲欧美日韩视频一区| 国产精品毛片一区二区在线看舒淇| 亚洲一二三区视频在线观看| 久久国产精品影院| 国产在线精品一区二区夜色| 亚洲 欧美 日韩 国产综合 在线| 欧美精选视频在线观看| 3d精品h动漫啪啪一区二区| 乡村艳史在线观看| 久久国内精品一国内精品| 黑人精品一区二区| 欧美日韩精品一二三区| 日韩少妇裸体做爰视频| 国产精品久久久久国产精品日日| 一级黄色电影片| 蜜桃91丨九色丨蝌蚪91桃色| 成人免费在线网| 国产精品国内免费一区二区三区| 精品无人区一区二区三区| 日日夜夜精品| 日本人成精品视频在线| 污污视频在线| 色伦专区97中文字幕| 午夜视频免费在线| 欧美一二三四区在线| 在线观看亚洲黄色| 欧美日韩一区二区三区在线免费观看| 欧美一级特黄高清视频| 久久午夜免费电影| 免费a v网站| 国产精品1区2区3区在线观看| 人妻无码视频一区二区三区| 影音先锋久久久| 9191国产视频| 亚洲澳门在线| 亚洲v欧美v另类v综合v日韩v| 五月激激激综合网色播| 国产精品视频一区二区三区经| 久久wwww| 成人性生交xxxxx网站| 国产一区二区色噜噜| 国产999精品久久久| www.日韩| 欧美一区二区三区图| 91豆花视频在线播放| 欧美猛交ⅹxxx乱大交视频| 黄色免费网站在线| 丝袜情趣国产精品| 98在线视频| www.日韩av.com| 日本黄色片在线观看| 日韩中文字幕网站| 黄色网页在线免费观看| 日韩一区二区在线视频| 在线观看黄av| 日韩一二三在线视频播| 九色porny在线| 精品久久久av| 在线免费观看a视频| 操人视频在线观看欧美| 香蕉久久aⅴ一区二区三区| 久久天天躁狠狠躁夜夜爽蜜月| 精品孕妇一区二区三区| 美女av一区二区| 青青青草视频在线| 精品自拍视频在线观看| 免费不卡av| 4p变态网欧美系列| 欧美aaa视频| 国产一区香蕉久久| 九色精品蝌蚪| 国产精品免费一区二区三区在线观看| 成人在线视频你懂的| 国产一区免费观看| 杨幂一区二区三区免费看视频| 日韩黄色影视| 国产精品毛片久久| 国产精品无码免费专区午夜| 最新国产拍偷乱拍精品| 黄色片视频在线免费观看| 首页国产欧美久久| 中文字幕久久av| 国产99一区视频免费| 国产熟女高潮一区二区三区| 久久精品夜色噜噜亚洲aⅴ| 欧美乱大交做爰xxxⅹ小说| 亚洲色图丝袜美腿| 日本网站在线免费观看| 欧美自拍偷拍午夜视频| 国产乱码精品一区二区| 亚洲精品一区二区三区在线观看 | 国产午夜精品美女视频明星a级| 香蕉视频在线播放| 久久久久国产一区二区三区| 最新日韩三级| 99www免费人成精品| 九九热线有精品视频99| 女同性恋一区二区| 国产欧美日韩综合一区在线播放 | 亚洲午夜小视频| 成人在线免费看| 欧美日韩国产成人高清视频| 美脚恋feet久草欧美| 亚洲精品免费av| 久久91麻豆精品一区| 日韩精品福利片午夜免费观看| 欧美专区18| 韩国三级hd中文字幕有哪些| 久久综合国产精品| 免费三片在线播放| 欧美三级韩国三级日本三斤| 刘玥91精选国产在线观看| 中文字幕久精品免费视频| 波多野结衣乳巨码无在线观看| 国产精品欧美一区二区| 激情亚洲另类图片区小说区| 一卡二卡3卡四卡高清精品视频| 亚洲国产高清视频| 亚洲黄色av片| 国产校园另类小说区| 日韩精品一区三区| 欧美日韩国产高清一区二区三区| 深夜福利视频在线观看| 操人视频在线观看欧美| av在线不卡精品| 久久精品国产综合精品| 欧美日韩影院| 亚洲视频一二三四| 国产女主播视频一区二区| 国产美女激情视频| 亚洲福利视频网站| a免费在线观看| 国产精品一区久久| 综合综合综合综合综合网| 毛片在线播放视频| 国产不卡一区视频| chinese全程对白| 欧美天堂一区二区三区| 欧美在线一卡| 97视频在线播放| 99国产精品免费网站| 91传媒免费视频| 国产一区二区三区黄视频 | 精品久久人人做人人爱| a毛片在线看免费观看| 91色视频在线导航| 天天色综合色| 不卡中文字幕在线观看| 国产精品不卡一区| 91成人一区二区三区| 日韩在线国产精品| 美女久久久久久| 亚洲国产欧美一区二区三区不卡| 日韩有码一区二区三区| 美女被到爽高潮视频| 欧美日韩亚洲91| 亚洲色图欧美视频| 欧美又大又硬又粗bbbbb| 亚洲裸色大胆大尺寸艺术写真| 久久综合九色综合88i| 2023国产精品视频| 色av性av丰满av| 亚洲一级片在线看| 香蕉久久免费电影| 日韩精品一线二线三线| 老司机精品视频一区二区三区| 91视频最新网址| 欧美精品黑人性xxxx| www红色一片_亚洲成a人片在线观看_| 亚洲一区美女视频在线观看免费| 午夜精品久久| 亚洲成年人在线观看| 欧美日韩中文字幕综合视频| 国产免费永久在线观看| 国产综合视频在线观看| 女主播福利一区| a级一a一级在线观看| 色综合久久久久久久久| 91在线高清| 3d精品h动漫啪啪一区二区| 999亚洲国产精| 少妇无套高潮一二三区| 欧美精品aⅴ在线视频| 日本理论片午伦夜理片在线观看| 精品国产_亚洲人成在线| 丝袜脚交一区二区| 欧美视频www| 亚洲精品一区av在线播放| 91大神在线观看线路一区| 三级在线免费观看| 91麻豆国产香蕉久久精品| 最近中文字幕在线视频| 久久亚洲欧美日韩精品专区| 全球av集中精品导航福利| 一区二区三区 欧美| 夜夜嗨av一区二区三区网页| 色av男人的天堂免费在线 | 欧美尿孔扩张虐视频| 欧美精品aaaa| 亚洲影视在线播放| 国自产拍在线网站网址视频| 亚洲a∨日韩av高清在线观看| 999亚洲国产精| 免费看一级黄色| 日韩精品视频免费| 久久三级中文| 成人性视频欧美一区二区三区| 亚洲欧美激情一区二区| 欧美日韩影视| 丁香五月网久久综合| 蜜桃视频一区二区| 国产综合精品视频| 欧美成人性生活| 成人激情视频|