cquery最近改動與libclang.so一位元組補丁

原文:maskray.me/blog/2017-12-25-cquery-updates-and-libclang-one-byte-patch

cquery最近改動

cquery介紹參看使用cquery:C++ language server。

最近cquery改動比較多:

  • 可執行文件從build/app變到build/release/bin/cquery了,支持release/debug/asan等多種waf variants,使用RPATH
  • ./waf configure --bundled=5.0.1可以用上最新出爐的clang+llvm 5.0.1
  • Riatre把Windows構建修好了 #154
  • FreeBSD可以使用了 #155及third party庫改動,感謝ngkaho1234把sparsepp FreeBSD kvm搞定
  • 不需要在initializationOptions里指定resourceDir了,感謝jiegec的#137
  • 各種模板改進和function template/class template內函數引用的支持。支持了CXCursor_OverloadedDeclRef函數調用#174,但template call template clang-c介面沒有暴露相應信息,可能無解。
  • textDocument/hover信息把函數名插入到函數類型里。用了一些heuristics處理_Atomic decltype() throw()__attribute(()) typeof(),碰到-> int(*)()這種還是沒救的,數組括弧也不好,但大多數情況都顯示得不錯的。
  • 加入了實驗性的--enable-comments,檢索注釋。#183 #188 #191 注釋和原來的聲明信息一起顯示,帶來了UI的挑戰。
  • VSCode使用浮動窗口,顯示多行textDocument/hover不成問題。但Emacs lsp-mode和LanguageClient-neovim就遇到一些困難LanguageClient-neovim#224 lsp-ui#17
  • workspace/symbol模糊匹配 #182
  • danielmartin自己的repo加了實驗性的textDocument/formatting,格式化。這必須用clang C++ API,作者有一些顧慮。

用了一個O(n^2) sequence alignment演算法,根據編輯距離、camelCase等啟發因素排序候選符號(func,type,path,…)。以foo bar為模式會返回fooBar foobar foozbar等,而fooBar排在前面。Emacs xref-find-apropos會自作聰明地把模式用空格分割後當作正規表達式轉義,需要覆蓋掉。

libclang handleReference問題

下面介紹重點,libclang一位元組補丁。

#192

% cd /tmp% git clone https://github.com/nlohmann/json% cd json% (mkdir build; cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..)% ln -s build/compile_commands.json% nvim test/src/unit-constructor1.cpp # or other editor

cquery命令行選項加--log-file /tmp/cq.log可以看到

indexer.cc:1892 WARN| Indexing /tmp/json/test/src/unit-iterators2.cpp failed with errno=1libclang: crash detected during indexing TU

errno=1CXError_Failure,libclang使用crash recovery機制保護了SIGSEGV並返回CXError_Failure。這類源文件沒有任何hover、definition、references信息。

原因是libclang/CXIndexDataConsumer.cpp#L203

handleReference(ND, Loc, Cursor, dyn_cast_or_null<NamedDecl>(ASTNode.Parent), ASTNode.ContainerDC, ASTNode.OrigE, Kind);

dyn_cast_or_null<NamedDecl>(ASTNode.Parent)可能會NULL,在libclang/CXIndexDataConsumer.cpp#L935處

ContainerInfo Container; getContainerInfo(DC, Container);

getContainerInfocast DC,null pointer dereference而SIGSEGV。

補丁已經提交到上游,但clang+llvm 5.0.1剛剛發布,等這個patch又要好久。對於無法/不願意編譯clang+llvm的Linux ./waf configure --bundled-clang=5.0.1用戶。 我的修復方案是

# --bundled-clang=5.0.1% printf x4d | dd of=build/release/lib/clang+llvm-5.0.1-x86_64-linux-gnu-ubuntu-14.04/lib/libclang.so.5.0 obs=1 seek=$[0x47aece] conv=notrunc# --bundled-clang=4.0.0% printf x4d | dd of=build/release/lib/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04/lib/libclang.so.4.0 obs=1 seek=$[0x4172b5] conv=notrunc

What?

說明

bool CXIndexDataConsumer::handleReference(const NamedDecl *D, SourceLocation Loc, CXCursor Cursor, const NamedDecl *Parent, const DeclContext *DC, const Expr *E, CXIdxEntityRefKind Kind) { if (!CB.indexEntityReference) return false; if (!D) return false; if (Loc.isInvalid()) return false;

根據System V x86-64 ABI,這個成員函數的參數傳遞方式:

this: rdiD: rsiLoc: rdxCursor: stackParent: rcxDC: r8E: r9Kind: stack

cquery會設置CB.indexEntityReferenceOnIndexReference,因此該值保證非NULL。

找到if (!CB.indexEntityReference)這個檢查

% objdump -M intel -Cd build/release/lib/clang+llvm-5.0.1-x86_64-linux-gnu-ubuntu-14.04/lib/libclang.so.5 --start-address 0x47ae90 --stop-address 0x47b190...... 47aeb0: 45 31 ed xor r13d,r13d 47aeb3: 45 85 ff test r15d,r15d 47aeb6: 0f 84 19 03 00 00 je 47b1d5 47aebc: 48 85 ed test rbp,rbp 47aebf: 0f 84 10 03 00 00 je 47b1d5 47aec5: 49 8b 44 24 18 mov rax,QWORD PTR [r12+0x18] # load `IndexerCallbacks &CB`, which is actually a pointer 47aeca: 48 8b 40 38 mov rax,QWORD PTR [rax+0x38] # load CB.indexEntityReference 47aece: 48 85 c0 test rax,rax # a redundant check to see if it is null; we replace it with check of `DC` 47aed1: 0f 84 fe 02 00 00 je 47b1d5 <clang::cxindex::CXIndexDataConsumer::handleReference(clang::NamedDecl const*, clang::SourceLocation, CXCursor, clang::NamedDecl const*, clang::DeclContext const*, clang::Expr const*, CXIdxEntityRefKind)+0x345>

0x47aeca處的test rax,rax就是這個冗餘if,我們把它換成if (!DC)test r8,r8

48 85 c0 test rax,rax4d 85 c0 test r8,r8

修改一個位元組即可,即前面介紹的`printf+dd`。使用radare2的話可以用`r2 -nwqc wx 4d @ 0x47aece build/release/lib/clang+llvm-5.0.1-x86_64-linux-gnu-ubuntu-14.04/lib/libclang.so.5.0`

radare2真是不成器,關鍵時候test r8,r8彙編又掛了radare2#9071??


推薦閱讀:

為什麼Apple的Clang生成的LLVM IR比開源的Clang生成的IR要讀者友好?
LLVM每日談之九 談LLVM的學習
在編譯C語言代碼時,Clang跟gcc編譯器哪一個編譯出來的程序運行更快?特別是在浮點運算方面。
Clang Parser漫步(三)——declarator的解析
[貝聊科技]如何將 iOS 項目的編譯速度提高5倍

TAG:C | Clang | 逆向工程 |