使用LLVM插件改写MSAsmStmt时触发段错误问题排查
问题:LLVM插件处理MSAsmStmt时调用Rewriter.ReplaceText触发段错误
我编写了包含__asm内联汇编块的C代码,希望开发LLVM插件自动为汇编块插入.loc调试指令以支持单步调试。插件可检测到MSAsmStmt语句,但调用Rewriter.ReplaceText接口时触发段错误,问题定位到Rewriter.getRangeSize函数。已验证源范围有效且非宏展开,可正常读取AST,仅调用Rewriter时出错。使用M2 MacBook Pro,测试过clang 16.0.6和19.1.1版本。请问实现思路是否正确?为何会触发段错误?
原C代码
#include <inttypes.h> #include <stdio.h> uint8_t count1(uint32_t x) { int out; __asm { mov edx, [x] mov al, 0 next: cmp edx, 0 je done mov cl, dl and cl, 1 add al, cl shr edx, 1 jmp next done: mov out, al } return out; } int main() { uint32_t x = 0x5789ABCD; uint8_t cnt = count1(x); printf("Number of 1s in 0x%X: %hhu\n", x, cnt); }
目标转换后代码
#include <inttypes.h> #include <stdio.h> uint8_t count1(uint32_t x) { int out; __asm { .file 1 "main.c" .loc 1 7 mov edx, [x] .loc 1 8 mov al, 0 .loc 1 9 next: cmp edx, 0 .loc 1 10 je done .loc 1 11 mov cl, dl .loc 1 12 and cl, 1 .loc 1 13 add al, cl .loc 1 14 shr edx, 1 .loc 1 15 jmp next done: .loc 1 17 mov out, al } return out; } int main() { uint32_t x = 0x5789ABCD; uint8_t cnt = count1(x); printf("Number of 1s in 0x%X: %hhu\n", x, cnt); }
插件核心代码
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> { public: explicit MyASTVisitor(ASTContext *Context, Rewriter &R) : Context(Context), TheRewriter(R) {} bool VisitStmt(Stmt *S) { if (auto *Asm = dyn_cast<MSAsmStmt>(S)) { StringRef AsmString = Asm->getAsmString(); SourceLocation StartLoc = Asm->getBeginLoc(); SourceLocation EndLoc = Asm->getEndLoc(); bool result = TheRewriter.ReplaceText(SourceRange(StartLoc, EndLoc), AsmString); llvm::errs() << "Replace result: " << result << "\n"; } return true; } private: ASTContext *Context; Rewriter &TheRewriter; };
错误栈信息
0. Program arguments: /opt/homebrew/Cellar/llvm@16/16.0.6_1/bin/clang-16 -cc1 -triple x86_64-apple-macosx15.0.0 -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -emit-obj -mrelax-all -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name testfile.c -mrelocation-model pic -pic-level 2 -mframe-pointer=all -ffp-contract=on -fno-rounding-math -funwind-tables=2 -fcompatibility-qualified-id-block-type-checking -fvisibility-inlines-hidden-static-local-var -target-cpu penryn -tune-cpu generic -mllvm -treat-scalable-fixed-error-as-warning -debug-info-kind=standalone -dwarf-version=4 -debugger-tuning=lldb -target-linker-version 1053.12 -fcoverage-compilation-dir=/Users/jurajpetras/dev/asm_debug -resource-dir /opt/homebrew/Cellar/llvm@16/16.0.6_1/lib/clang/16 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/usr/local/include -internal-isystem /opt/homebrew/Cellar/llvm@16/16.0.6_1/lib/clang/16/include -internal-externc-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/usr/include -O0 -fdebug-compilation-dir=/Users/jurajpetras/dev/asm_debug -ferror-limit 19 -stack-protector 1 -fblocks -fencode-extended-block-signature -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fmax-type-align=16 -fcolor-diagnostics -fasm-blocks -load ./build/libasm_debug.dylib -add-plugin asm_debug -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /var/folders/88/yqhhhgms02vcwxdp_x2dgbp40000gn/T/testfile-0c3ab4.o -x c testfile.c 1. <eof> parser at end of file Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it): 0 libLLVM.dylib 0x0000000110344b20 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56 1 libLLVM.dylib 0x00000001103439a4 llvm::sys::RunSignalHandlers() + 112 2 libLLVM.dylib 0x00000001103451b4 SignalHandler(int) + 360 3 libsystem_platform.dylib 0x0000000199b08184 _sigtramp + 56 4 libasm_debug.dylib 0x00000001091de914 clang::Rewriter::getRangeSize(clang::CharSourceRange const&, clang::Rewriter::RewriteOptions) const + 212 5 libasm_debug.dylib 0x00000001091deb74 clang::Rewriter::getRangeSize(clang::SourceRange, clang::Rewriter::RewriteOptions) const + 36 6 libasm_debug.dylib 0x0000000108acc49c clang::Rewriter::ReplaceText(clang::SourceRange, llvm::StringRef) + 100 7 libasm_debug.dylib 0x0000000108acc30c (anonymous namespace)::MyASTVisitor::VisitStmt(clang::Stmt*) + 180 8 libasm_debug.dylib 0x0000000108acc22c clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::WalkUpFromStmt(clang::Stmt*) + 36 9 libasm_debug.dylib 0x0000000108acc170 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::WalkUpFromAsmStmt(clang::AsmStmt*) + 48 10 libasm_debug.dylib 0x0000000108aa3b3c clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::WalkUpFromMSAsmStmt(clang::MSAsmStmt*) + 48 11 libasm_debug.dylib 0x0000000108aa3978 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseMSAsmStmt(clang::MSAsmStmt*, llvm::SmallVectorImpl<llvm::PointerIntPair<clang::Stmt*, 1u, bool, llvm::PointerLikeTypeTraits<clang::Stmt*>, llvm::PointerIntPairInfo<clang::Stmt*, 1u, llvm::PointerLikeTypeTraits<clang::Stmt*>>>>*) + 80 12 libasm_debug.dylib 0x0000000108aa1184 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::dataTraverseNode(clang::Stmt*, llvm::SmallVectorImpl<llvm::PointerIntPair<clang::Stmt*, 1u, bool, llvm::PointerLikeTypeTraits<clang::Stmt*>, llvm::PointerIntPairInfo<clang::Stmt*, 1u, llvm::PointerLikeTypeTraits<clang::Stmt*>>>>*) + 144 13 libasm_debug.dylib 0x0000000108a67288 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseStmt(clang::Stmt*, llvm::SmallVectorImpl<llvm::PointerIntPair<clang::Stmt*, 1u, bool, llvm::PointerLikeTypeTraits<clang::Stmt*>, llvm::PointerIntPairInfo<clang::Stmt*, 1u, llvm::PointerLikeTypeTraits<clang::Stmt*>>>>*) + 672 14 libasm_debug.dylib 0x0000000108b4ef78 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseFunctionHelper(clang::FunctionDecl*) + 1404 15 libasm_debug.dylib 0x0000000108a61c3c clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseFunctionDecl(clang::FunctionDecl*) + 128 16 libasm_debug.dylib 0x0000000108a58914 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseDecl(clang::Decl*) + 2852 17 libasm_debug.dylib 0x0000000108ae473c clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseDeclContextHelper(clang::DeclContext*) + 216 18 libasm_debug.dylib 0x0000000108a6684c clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseTranslationUnitDecl(clang::TranslationUnitDecl*) + 564 19 libasm_debug.dylib 0x0000000108a58f14 clang::RecursiveASTVisitor<(anonymous namespace)::MyASTVisitor>::TraverseDecl(clang::Decl*) + 4388 20 libasm_debug.dylib 0x0000000108a57d8c (anonymous namespace)::MyASTConsumer::HandleTranslationUnit(clang::ASTContext&) + 52 21 libclang-cpp.dylib 0x00000001066383a8 clang::MultiplexConsumer::HandleTranslationUnit(clang::ASTContext&) + 52 22 libclang-cpp.dylib 0x0000000104911fa8 clang::ParseAST(clang::Sema&, bool, bool) + 752 23 libclang-cpp.dylib 0x00000001065ff750 clang::FrontendAction::Execute() + 112 24 libclang-cpp.dylib 0x0000000106582da8 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) + 868 25 libclang-cpp.dylib 0x0000000106677440 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) + 524 26 clang-16 0x00000001004c2ca4 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) + 1464 27 clang-16 0x00000001004bfb50 ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&) + 948 28 clang-16 0x00000001004beddc clang_main(int, char**) + 11192 29 dyld 0x0000000199750274 start + 2840 clang-16: error: unable to execute command: Segmentation fault: 11
编译命令
clang -Xclang -load -Xclang ./build/libasm_debug.dylib -Xclang -add-plugin -Xclang asm_debug testfile.c -target x86_64-apple-macos -fasm-blocks -g -O0
问题分析与解答
实现思路正确性
你的核心思路是可行的:通过RecursiveASTVisitor遍历AST定位MSAsmStmt,重写汇编内容插入.loc调试指令,这是给内联汇编补全调试信息的常规技术方向。但当前代码的问题在于对MSAsmStmt的源范围处理和Rewriter的使用细节。
段错误原因
从错误栈和代码来看,段错误的核心诱因是Rewriter::getRangeSize访问了无效内存,具体可能有以下几点:
- MSAsmStmt的源范围不适用直接替换
MSAsmStmt的getBeginLoc()和getEndLoc()仅覆盖__asm关键字和外层大括号的范围,并非内部汇编指令的完整文本范围。Clang对MS风格汇编的源位置存储有特殊逻辑,直接用这个范围调用ReplaceText会导致Rewriter计算范围时访问无效内存区域。 - Rewriter初始化不完整
如果插件中Rewriter未正确绑定ASTContext的SourceManager和LangOptions,调用getRangeSize时会因缺少源文件上下文而访问空指针,触发段错误。 - 汇编文本与源范围不匹配
Asm->getAsmString()返回的是Clang解析后的标准化汇编字符串,和原始源代码中的文本格式、长度可能存在差异,直接替换会导致Rewriter计算范围时出现偏移错误。
修复建议
- 验证源范围有效性
在调用ReplaceText前,添加以下代码确认源位置可访问:SourceManager &SM = Context->getSourceManager(); if (!SM.isValidFileID(StartLoc.getFileID())) { llvm::errs() << "Invalid source location\n"; return true; } // 打印位置偏移,确认范围合理 llvm::errs() << "Start offset: " << SM.getFileOffset(StartLoc) << ", End offset: " << SM.getFileOffset(EndLoc) << "\n"; - 正确初始化Rewriter
在ASTConsumer的Initialize方法中绑定SourceManager和LangOptions:void Initialize(ASTContext &Context) override { TheRewriter.setSourceMgr(Context.getSourceManager(), Context.getLangOpts()); } - 调整替换范围
不要替换整个__asm块,而是只替换大括号内的汇编内容。可以通过手动定位大括号的位置,或者遍历MSAsmStmt的子节点获取内部指令的源范围。 - 使用CharSourceRange
调用ReplaceText时,使用字符范围而非token范围,避免格式问题导致的计算错误:auto Range = CharSourceRange::getTokenRange(StartLoc, EndLoc); bool result = TheRewriter.ReplaceText(Range, AsmString);
内容的提问来源于stack exchange,提问作者Juraj
相关产品推荐
相关产品推荐

