**背景:**本人主要在做C++ SDK的開(kāi)發(fā),需要給到業(yè)務(wù)端去集成,在集成的過(guò)程中可能會(huì)出現(xiàn)某些功能性bug,即沒(méi)有得到想要的結(jié)果。那怎么調(diào)試?
**分析:**這種問(wèn)題其實(shí)調(diào)試起來(lái)稍微有點(diǎn)困難,它不像crash,當(dāng)發(fā)生crash時(shí)還能拿到堆棧信息去分析,然而功能性bug沒(méi)有crash,也就沒(méi)法捕捉對(duì)應(yīng)到當(dāng)時(shí)的堆棧信息。因?yàn)椴皇窃诒镜兀矝](méi)法用編譯器debug。那思路就剩log了,一種方式是考慮在SDK內(nèi)部的關(guān)鍵路徑下打印詳細(xì)的log,當(dāng)出現(xiàn)問(wèn)題時(shí)拿到log去分析。然而總有漏的時(shí)候,誰(shuí)能保證log一定打的很全面,很有可能問(wèn)題就出現(xiàn)在沒(méi)有l(wèi)og的函數(shù)中。
**解決:**基于上面的背景和問(wèn)題分析,考慮是否能做一個(gè)全鏈路追蹤的方案,把打印出整個(gè)SDK的調(diào)用路徑,從哪個(gè)函數(shù)進(jìn)入,從哪個(gè)函數(shù)退出等。
**想法1:**可以考慮在SDK的每個(gè)接口都加一個(gè)context結(jié)構(gòu)體參數(shù),記錄下來(lái)函數(shù)的調(diào)用路徑,這可能是比較通用有效的方案,但是SDK接口已經(jīng)固定了,更改接口要面臨的困難很大,業(yè)務(wù)端基本不會(huì)同意,所以這種方案不適合我們現(xiàn)有情況,當(dāng)然一個(gè)從0開(kāi)始建設(shè)的中間件和SDK可以考慮考慮。
**想法2:**有沒(méi)有一種不用改接口,還能追蹤到函數(shù)調(diào)用路徑的方案?
繼續(xù)沿著這個(gè)思路繼續(xù)調(diào)研,我找到了gcc和clang編譯器的一個(gè)編譯參數(shù):-finstrument-functions,編譯時(shí)添加此參數(shù)會(huì)在函數(shù)的入口和出口處觸發(fā)一個(gè)固定的回調(diào)函數(shù),即:
__cyg_profile_func_enter(void *callee, void *caller);
__cyg_profile_func_exit(void *callee, void *caller);
參數(shù)就是callee和caller的地址,那怎么將地址解析成對(duì)應(yīng)函數(shù)名?可以使用dladdr函數(shù):
int dladdr(const void *addr, Dl_info *info);
看下下面的代碼:
// tracing.cc
#include
#include // for dladdr
#include
#include
#include
#ifndef NO_INSTRUMENT
#define NO_INSTRUMENT __attribute__((no_instrument_function))
#endif
extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_enter(void *callee, void *caller) {
Dl_info info;
if (dladdr(callee, &info)) {
int status;
const char *name;
char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
if (status == 0) {
name = demangled ? demangled : "[not demangled]";
} else {
name = info.dli_sname ? info.dli_sname : "[no dli_sname nd std]";
}
printf("enter %s (%s)\\n", name, info.dli_fname);
if (demangled) {
free(demangled);
demangled = NULL;
}
}
}
extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_exit(void *callee, void *caller) {
Dl_info info;
if (dladdr(callee, &info)) {
int status;
const char *name;
char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
if (status == 0) {
name = demangled ? demangled : "[not demangled]";
} else {
name = info.dli_sname ? info.dli_sname : "[no dli_sname and std]";
}
printf("exit %s (%s)\\n", name, info.dli_fname);
if (demangled) {
free((void *)demangled);
demangled = NULL;
}
}
}
這是測(cè)試文件:
// test_trace.cc
void func1() {}
void func() { func1(); }
int main() { func(); }
將test_trace.cc和tracing.cc文件同時(shí)編譯鏈接,即可達(dá)到鏈路追蹤的目的:
g++ test_trace.cc tracing.cc -std=c++14 -finstrument-functions -rdynamic -ldl;./a.out
輸出:enter main (./a.out)
enter func() (./a.out)
enter func1() (./a.out)
exit func1() (./a.out)
exit func() (./a.out)
exit main (./a.out)
如果在func()中調(diào)用了一些其他的函數(shù)呢?
#include
#include
void func1() {}
void func() {
std::vector<int> v{1, 2, 3};
std::cout << v.size();
func1();
}
int main() { func(); }
再重新編譯后輸出會(huì)是這樣:
enter [no dli_sname nd std] (./a.out)
enter [no dli_sname nd std] (./a.out)
exit [no dli_sname and std] (./a.out)
exit [no dli_sname and std] (./a.out)
enter main (./a.out)
enter func() (./a.out)
enter std::allocator<int>::allocator() (./a.out)
enter __gnu_cxx::new_allocator<int>::new_allocator() (./a.out)
exit __gnu_cxx::new_allocator<int>::new_allocator() (./a.out)
exit std::allocator<int>::allocator() (./a.out)
enter std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (./a.out)
enter std::_Vector_base<int, std::allocator<int> >::_Vector_base(std::allocator<int> const&) (./a.out)
enter std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Vector_impl(std::allocator<int> const&) (./a.out)
enter std::allocator<int>::allocator(std::allocator<int> const&) (./a.out)
enter __gnu_cxx::new_allocator<int>::new_allocator(__gnu_cxx::new_allocator<int> const&) (./a.out)
exit __gnu_cxx::new_allocator<int>::new_allocator(__gnu_cxx::new_allocator<int> const&) (./a.out)
exit std::allocator<int>::allocator(std::allocator<int> const&) (./a.out)
exit std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Vector_impl(std::allocator<int> const&) (./a.out)
exit std::_Vector_base<int, std::allocator<int> >::_Vector_base(std::allocator<int> const&) (./a.out)
上面我只貼出了部分信息,這顯然不是我們想要的,我們只想要顯示自定義的函數(shù)調(diào)用路徑,其他的都想要過(guò)濾掉,怎么辦?
這里可以將自定義的函數(shù)都加一個(gè)統(tǒng)一的前綴,在打印時(shí)只打印含有前綴的符號(hào),這種個(gè)人認(rèn)為是比較通用的方案。
下面是我過(guò)濾掉std和gnu子串的代碼:
if (!strcasestr(name, "std") && !strcasestr(name, "gnu")) {
printf("enter %s (%s)\\n", name, info.dli_fname);
}
if (!strcasestr(name, "std") && !strcasestr(name, "gnu")) {
printf("exit %s (%s)\\n", name, info.dli_fname);
}
重新編譯后就會(huì)輸出我想要的結(jié)果:
g++ test_trace.cc tracing.cc -std=c++14 -finstrument-functions -rdynamic -ldl;./a.out
輸出:enter main (./a.out)
enter func() (./a.out)
enter func1() (./a.out)
exit func1() (./a.out)
exit func() (./a.out)
exit main (./a.out)
還有一種方式是在編譯時(shí)使用下面的參數(shù):
-finstrument-functions-exclude-file-list
它可以排除不想要做trace的文件,但是這個(gè)參數(shù)只在gcc中可用,在clang中卻不支持 ,所以上面的字符串過(guò)濾方式更通用一些。
上面只能拿到函數(shù)的名字,不能定位到具體的文件和行號(hào),如果想要獲得更多信息,需要結(jié)合bfd系列參數(shù)(bfd_find_nearest_line)和libunwind一起使用,大家可以繼續(xù)研究。。。
tips1: 這是一篇拋磚引玉的文章,本人不是后端開(kāi)發(fā),據(jù)我所知后端C++中有很多成熟的trace方案,大家有更好的方案可以留言,分享一波。
tips2: 上面的方案可以達(dá)到鏈路追蹤的目的,但本人最后沒(méi)有應(yīng)用到項(xiàng)目中,因?yàn)楸救嗽谧龅捻?xiàng)目對(duì)性能要求較高,使用此種方案會(huì)使整個(gè)SDK性能下降嚴(yán)重,無(wú)法滿(mǎn)足需求正常運(yùn)行。于是暫時(shí)放棄了鏈路追蹤的這個(gè)想法。
本文的知識(shí)點(diǎn)還是值得了解一下的,大家或許會(huì)用得到。在研究的過(guò)程中我也發(fā)現(xiàn)了一個(gè)基于此種方案的開(kāi)源項(xiàng)目(call-stack-logger),感興趣的也可以去了解了解。
-
堆棧
+關(guān)注
關(guān)注
0文章
183瀏覽量
20043 -
編譯器
+關(guān)注
關(guān)注
1文章
1656瀏覽量
49898 -
BUG
+關(guān)注
關(guān)注
0文章
156瀏覽量
15961
發(fā)布評(píng)論請(qǐng)先 登錄
光鏈路實(shí)時(shí)監(jiān)測(cè)倒換系統(tǒng)
全鏈路壓測(cè)一招搞定,阿里云性能測(cè)試鉑金版發(fā)布
基于分布式調(diào)用鏈監(jiān)控技術(shù)的全息排查功能
淺談OPPO設(shè)計(jì)的首個(gè)Android全鏈路色彩管理系統(tǒng)
2021 OPPO開(kāi)發(fā)者大會(huì):全鏈路運(yùn)營(yíng)

安歌科技Enotek如何實(shí)現(xiàn)全鏈路智能物流解決方案?
介紹Node.js應(yīng)用全鏈路信息獲取的方法
淺談C語(yǔ)言與C++的前世今生

C++之父新作帶你勾勒現(xiàn)代C++地圖

C++簡(jiǎn)史:C++是如何開(kāi)始的

評(píng)論