花費了比較長的時間,解決了編譯過程中遇到的許多問題后,終于把這個開源的工程編譯好了,運行post build 腳本將需要的鏈接文件拷貝好。正當我以為沒有任何問題了,雙擊可執行程序運行。
結果運行起來的時候報錯了,提示無法正常啟動,根據這個這個報錯的提示,網上搜了一下,都說是缺少dll庫導致的,于是將post build腳本又運行了一遍,重新雙擊打開可執行程序,還是報一樣的錯。
那么是缺少那個dll庫導致的呢?于是我打開系統的事件查看器,從里面找到了應用程序錯誤事件,點擊看到了詳細信息,從描述來看出錯的模塊是libprotobufd.dll,是在運行這個dll的過程中出現的異常,并非缺少dll導致。
{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "(msvc) launch","type": "cppvsdbg","request": "launch","program": "D:\\Kicad\\kicad-source-mirror\\build\\out\\bin\\kicad.exe"}]
}
為了進一步弄清楚這個庫出錯的原因和位置,于是在vscode編輯器中將C/C++相關的插件安裝上,設置好debug條件,運行debug功能后,跳轉到了出現異常的代碼位置。
從給的錯誤信息可以看出來_Pnext是一個非法值,那么這個值為什么是一個非法值,目前猜測它是一個空指針,或者指向的內存不對,目前只是有這樣一個概念,帶著這樣的疑惑繼續分析原因。
_EXPORT_STD template <class _Ty, class _Other = _Ty>
_CONSTEXPR20 _Ty exchange(_Ty& _Val, _Other&& _New_val)noexcept(conjunction_v<is_nothrow_move_constructible<_Ty>, is_nothrow_assignable<_Ty&, _Other>>) {// assign _New_val to _Val, return previous _Val_Ty _Old_val = static_cast<_Ty&&>(_Val);_Val = static_cast<_Other&&>(_New_val);return _Old_val;
}
exchange的作用是將第一個參數設置成第二個參數的值,然后返回第一個參數的值,所以auto _Pnext = _STD exchange(_Myproxy->_Myfirstiter, nullptr)可以理解為:
_Pnext = _Myproxy->_Myfirstiter;
_Myproxy->_Myfirstiter = nullptr;
上述只是簡單理解這個函數的功能,這樣看這里也沒有問題
到了這里我們還是無法知道_Pnext指向的區域為什么變成非法的,還有就是_Pnext到底指向的是什么,也就是它到底是什么,怎么來的,于是根據發生異常的調用棧進一步追蹤,看一下具體是怎么發生的。根據調用棧我們看到了一個明顯的可能有問題的地方那就是std::string::~basic_string(),這里先猜測string對象析構的時候發生了異常,析構的時候一般會去釋放資源,是不是釋放的資源出來問題,所以才導致的異常。
bool EncodedDescriptorDatabase::DescriptorIndex::AddSymbol(absl::string_view symbol) {SymbolEntry entry = {static_cast<int>(all_values_.size() - 1),EncodeString(symbol)};std::string entry_as_string = entry.AsString(*this);// We need to make sure not to violate our map invariant.// If the symbol name is invalid it could break our lookup algorithm (which// relies on the fact that '.' sorts before all other characters that are// valid in symbol names).if (!ValidateSymbolName(symbol)) {ABSL_LOG(ERROR) << "Invalid symbol name: " << entry_as_string;return false;}auto iter = FindLastLessOrEqual(&by_symbol_, entry);if (!CheckForMutualSubsymbols(entry_as_string, &iter, by_symbol_.end(),*this)) {return false;}// Same, but on by_symbol_flat_auto flat_iter =FindLastLessOrEqual(&by_symbol_flat_, entry, by_symbol_.key_comp());if (!CheckForMutualSubsymbols(entry_as_string, &flat_iter,by_symbol_flat_.end(), *this)) {return false;}// OK, no conflicts.// Insert the new symbol using the iterator as a hint, the new entry will// appear immediately before the one the iterator is pointing at.by_symbol_.insert(iter, entry);return true;
}
于是繼續追蹤代碼里面是哪個執行造成的,還是根據前面的調用棧,我們看到執行完AddSymbol后,會調用~basic_string(),那么根據上面的代碼來看,應該是entry_as_string這個對象的使用有問題。
根據debug信息,看到entry_as_string的內存有問題,到這來還不知道是因為被釋放了,還是本身創建出來的就有問題,繼續追蹤這個是怎么創建出來的。
std::string AsString(const DescriptorIndex& index) const {auto p = package(index);return absl::StrCat(p, p.empty() ? "" : ".", symbol(index));}
std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c) {std::string result;// Use uint64_t to prevent size_t overflow. We assume it is not possible for// in memory strings to overflow a uint64_t.constexpr uint64_t kMaxSize = uint64_t{std::numeric_limits<size_t>::max()};const uint64_t result_size = static_cast<uint64_t>(a.size()) +static_cast<uint64_t>(b.size()) +static_cast<uint64_t>(c.size());ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow");strings_internal::STLStringResizeUninitialized(&result, static_cast<size_t>(result_size));char* const begin = &result[0];char* out = begin;out = Append(out, a);out = Append(out, b);out = Append(out, c);assert(out == begin + result.size());return result;
}
這里重點是std::string result;這個result有問題,但是有實在看不出代碼哪里有問題,于是只能繼續debug,進入這個函數后一步步運行,看一下具體是哪里出了問題。
在運行的過程中,這個result的就沒有顯示出來,它的內存是什么,指向哪里根本不知道。
只知道這個函數返回的時候,entry_as_string指向了一個錯誤的內存,最終導致崩潰,目前從debug的過程來看不出具體是什么原因導致的問題。
于是寫了一段類似的測試代碼,看看能不能復現到這樣的問題,也可以對比這個過程中有哪些不一樣,看看能不能找出根本原因。
測試代碼在運行的過程中沒有出錯,只能看到它們不一樣的地方
異常代碼 | 測試代碼 | |
1 | std::string result 沒有跑構造函數 | std::string result 正常跑構造函數 |
2 | debug過程沒有顯示result的信息 | debug過程會顯示result的信息 |
3 | 函數里面的begin、out等變量也沒有信息 | 函數里面的begin指向和顯示沒有問題 |
目前猜測可能是std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c) 函數里面的代碼被優化了導致的。