成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

C++的全鏈路追蹤方案,稍微有點高端

開發 后端
本文的知識點還是值得了解一下的,大家或許會用得到。在研究的過程中我也發現了一個基于此種方案的開源項目(call-stack-logger),感興趣的也可以去了解了解。

背景:本人主要在做C++ SDK的開發,需要給到業務端去集成,在集成的過程中可能會出現某些功能性bug,即沒有得到想要的結果。那怎么調試?

分析:這種問題其實調試起來稍微有點困難,它不像crash,當發生crash時還能拿到堆棧信息去分析,然而功能性bug沒有crash,也就沒法捕捉對應到當時的堆棧信息。因為不是在本地,也沒法用編譯器debug。那思路就剩log了,一種方式是考慮在SDK內部的關鍵路徑下打印詳細的log,當出現問題時拿到log去分析。然而總有漏的時候,誰能保證log一定打的很全面,很有可能問題就出現在沒有log的函數中。

解決:基于上面的背景和問題分析,考慮是否能做一個全鏈路追蹤的方案,把打印出整個SDK的調用路徑,從哪個函數進入,從哪個函數退出等。

想法1:可以考慮在SDK的每個接口都加一個context結構體參數,記錄下來函數的調用路徑,這可能是比較通用有效的方案,但是SDK接口已經固定了,更改接口要面臨的困難很大,業務端基本不會同意,所以這種方案不適合我們現有情況,當然一個從0開始建設的中間件和SDK可以考慮考慮。

想法2:有沒有一種不用改接口,還能追蹤到函數調用路徑的方案?

繼續沿著這個思路繼續調研,我找到了gcc和clang編譯器的一個編譯參數:-finstrument-functions,編譯時添加此參數會在函數的入口和出口處觸發一個固定的回調函數,即:

  1. __cyg_profile_func_enter(void *callee, void *caller); 
  2. __cyg_profile_func_exit(void *callee, void *caller); 

參數就是callee和caller的地址,那怎么將地址解析成對應函數名?可以使用dladdr函數:

  1. int dladdr(const void *addr, Dl_info *info); 

看下下面的代碼:

  1. // tracing.cc 
  2.  
  3. #include <cxxabi.h> 
  4. #include <dlfcn.h>  // for dladdr 
  5. #include <stdio.h> 
  6. #include <stdlib.h> 
  7. #include <string.h> 
  8.  
  9. #ifndef NO_INSTRUMENT 
  10. #define NO_INSTRUMENT __attribute__((no_instrument_function)) 
  11. #endif 
  12.  
  13. extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_enter(void *callee, void *caller) { 
  14.     Dl_info info; 
  15.     if (dladdr(callee, &info)) { 
  16.         int status; 
  17.         const char *name
  18.         char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); 
  19.         if (status == 0) { 
  20.             name = demangled ? demangled : "[not demangled]"
  21.         } else { 
  22.             name = info.dli_sname ? info.dli_sname : "[no dli_sname nd std]"
  23.         } 
  24.  
  25.         printf("enter %s (%s)\n"name, info.dli_fname); 
  26.  
  27.         if (demangled) { 
  28.             free(demangled); 
  29.             demangled = NULL
  30.         } 
  31.     } 
  32.  
  33. extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_exit(void *callee, void *caller) { 
  34.     Dl_info info; 
  35.     if (dladdr(callee, &info)) { 
  36.         int status; 
  37.         const char *name
  38.         char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); 
  39.         if (status == 0) { 
  40.             name = demangled ? demangled : "[not demangled]"
  41.         } else { 
  42.             name = info.dli_sname ? info.dli_sname : "[no dli_sname and std]"
  43.         } 
  44.         printf("exit %s (%s)\n"name, info.dli_fname); 
  45.  
  46.         if (demangled) { 
  47.             free((void *)demangled); 
  48.             demangled = NULL
  49.         } 
  50.     } 

這是測試文件:

  1. // test_trace.cc 
  2. void func1() {} 
  3.  
  4. void func() { func1(); } 
  5.  
  6. int main() { func(); } 
  7. 將test_trace.cc和tracing.cc文件同時編譯鏈接,即可達到鏈路追蹤的目的: 
  8. g++ test_trace.cc tracing.cc -std=c++14 -finstrument-functions -rdynamic -ldl;./a.out 
  9. 輸出:enter main (./a.out
  10. enter func() (./a.out
  11. enter func1() (./a.out
  12. exit func1() (./a.out
  13. exit func() (./a.out
  14. exit main (./a.out

如果在func()中調用了一些其他的函數呢?

  1. #include <iostream> 
  2. #include <vector> 
  3.  
  4. void func1() {} 
  5.  
  6. void func() { 
  7.     std::vector<int> v{1, 2, 3}; 
  8.     std::cout << v.size(); 
  9.     func1(); 
  10.  
  11. int main() { func(); } 

再重新編譯后輸出會是這樣:

  1. enter [no dli_sname nd std] (./a.out
  2. enter [no dli_sname nd std] (./a.out
  3. exit [no dli_sname and std] (./a.out
  4. exit [no dli_sname and std] (./a.out
  5. enter main (./a.out
  6. enter func() (./a.out
  7. enter std::allocator<int>::allocator() (./a.out
  8. enter __gnu_cxx::new_allocator<int>::new_allocator() (./a.out
  9. exit __gnu_cxx::new_allocator<int>::new_allocator() (./a.out
  10. exit std::allocator<int>::allocator() (./a.out
  11. enter std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (./a.out
  12. enter std::_Vector_base<int, std::allocator<int> >::_Vector_base(std::allocator<int> const&) (./a.out
  13. enter std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Vector_impl(std::allocator<int> const&) (./a.out
  14. enter std::allocator<int>::allocator(std::allocator<int> const&) (./a.out
  15. enter __gnu_cxx::new_allocator<int>::new_allocator(__gnu_cxx::new_allocator<int> const&) (./a.out
  16. exit __gnu_cxx::new_allocator<int>::new_allocator(__gnu_cxx::new_allocator<int> const&) (./a.out
  17. exit std::allocator<int>::allocator(std::allocator<int> const&) (./a.out
  18. exit std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Vector_impl(std::allocator<int> const&) (./a.out
  19. exit std::_Vector_base<int, std::allocator<int> >::_Vector_base(std::allocator<int> const&) (./a.out

上面我只貼出了部分信息,這顯然不是我們想要的,我們只想要顯示自定義的函數調用路徑,其他的都想要過濾掉,怎么辦?

這里可以將自定義的函數都加一個統一的前綴,在打印時只打印含有前綴的符號,這種個人認為是比較通用的方案。

下面是我過濾掉std和gnu子串的代碼:

  1. if (!strcasestr(name"std") && !strcasestr(name"gnu")) { 
  2.     printf("enter %s (%s)\n"name, info.dli_fname); 
  3.  
  4. if (!strcasestr(name"std") && !strcasestr(name"gnu")) { 
  5.     printf("exit %s (%s)\n"name, info.dli_fname); 

重新編譯后就會輸出我想要的結果:

  1. g++ test_trace.cc tracing.cc -std=c++14 -finstrument-functions -rdynamic -ldl;./a.out 
  2. 輸出:enter main (./a.out
  3. enter func() (./a.out
  4. enter func1() (./a.out
  5. exit func1() (./a.out
  6. exit func() (./a.out
  7. exit main (./a.out

還有一種方式是在編譯時使用下面的參數:

  1. -finstrument-functions-exclude-file-list 

它可以排除不想要做trace的文件,但是這個參數只在gcc中可用,在clang中卻不支持 ,所以上面的字符串過濾方式更通用一些。

上面只能拿到函數的名字,不能定位到具體的文件和行號,如果想要獲得更多信息,需要結合bfd系列參數(bfd_find_nearest_line)和libunwind一起使用,大家可以繼續研究。。。

tips1:這是一篇拋磚引玉的文章,本人不是后端開發,據我所知后端C++中有很多成熟的trace方案,大家有更好的方案可以留言,分享一波。

tips2:上面的方案可以達到鏈路追蹤的目的,但本人最后沒有應用到項目中,因為本人在做的項目對性能要求較高,使用此種方案會使整個SDK性能下降嚴重,無法滿足需求正常運行。于是暫時放棄了鏈路追蹤的這個想法。

本文的知識點還是值得了解一下的,大家或許會用得到。在研究的過程中我也發現了一個基于此種方案的開源項目(call-stack-logger),感興趣的也可以去了解了解。

 

責任編輯:武曉燕 來源: 程序喵大人
相關推薦

2023-10-16 23:43:52

云原生可觀測性

2023-01-30 22:34:44

Node.js前端

2022-07-22 07:59:17

日志方案

2022-05-23 08:23:24

鏈路追蹤SleuthSpring

2022-05-19 13:33:39

系統客戶端鏈路追蹤

2024-12-16 13:34:35

2022-05-25 08:23:32

ZipKinTwitter開源項目

2025-03-11 14:16:09

2025-01-20 08:10:00

微服務架構SLF4J

2024-09-06 12:24:19

2025-05-26 08:50:00

SLF4JMDC全鏈路追蹤

2021-11-18 10:01:00

Istio 全鏈路灰度微服務框架

2022-12-05 19:15:12

得物云原生全鏈路

2018-07-03 15:56:59

騰訊

2022-09-15 10:03:42

Jaeger分布式追蹤系統

2023-08-24 22:13:31

2020-12-16 09:24:18

Skywalking分布式鏈路追蹤

2024-06-07 13:04:31

2024-01-05 00:29:36

全鏈路灰度發布云原生

2023-08-09 08:18:22

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久一区二区三区四区 | 免费一区 | 韩国久久精品 | 亚洲精品99999 | 欧美v日韩| 91精品国产综合久久婷婷香蕉 | 国产精品久久久久久亚洲调教 | 日韩在线免费 | 91久久综合亚洲鲁鲁五月天 | 91在线成人 | 亚洲国产精品美女 | 精品一区在线看 | 福利av在线 | 亚洲视频欧美视频 | 精品中文字幕久久 | 天天色综| 凹凸日日摸日日碰夜夜 | 亚洲在线一区二区 | 久久久久久高清 | 国产精品亚洲精品日韩已方 | 一区二区三区欧美 | 国产福利在线播放麻豆 | 国产精品视频一二三区 | 在线国产一区二区三区 | 国产2区 | 欧美国产激情二区三区 | 狠狠干天天干 | 国产精品美女一区二区 | 国产精品资源在线 | 狠狠av | 亚洲h在线观看 | 黄色大片网站 | 特黄特色大片免费视频观看 | 最新国产精品 | 国产在线视频一区二区董小宛性色 | 色999视频 | 色888www视频在线观看 | 欧洲精品一区 | 日韩在线视频一区 | 天堂视频中文在线 | 91在线观看免费视频 |