Clang调试CUDA代码
Clang调试CUDA代码全过程
有空再进行编辑,最近有点忙,抱歉
使用的llvm4.0+Clang4.0的版本,依据的是上次发的llvm4.0和clang4.0源码安装的教程https://www.cnblogs.com/jourluohua/p/9554995.html
其中Clang的源码位于llvm-4.0.0.src/tools/clang/文件夹中,在本文中,我们的base_dir就是此目录,即base_dir=llvm-4.0.0.src/tools/clang
Clang 是 LLVM 的一个编译器前端,是使用C++开发的一个优秀软件。因此分析clang的源码,可以从调试clang的main函数作为入口开始。
使用命令进入gdb模式
$gdb ./clang++
设置输入参数
(gdb) set args apxy.cu -o apxy --cuda-gpu-arch=sm_50 --cuda-path=/usr/local/cuda -L/usr/local/cuda/lib64/ -lcudart_static -ldl -lrt -pthread
这里有个很关键的点,gdb必须设置为子线程模式,否则一直停留在父线程上,无法进入想要的函数。(Notice:这个设置仅当次有效,如果需要长期有效,请修改配置文件)
(gdb) set follow-fork-mode child
在main函数上设置断点
(gdb) b main
提示Breakpoint 1 at 0x1be2b27: file base_dir/tools/driver/driver.cpp, line 308.
因此Clang的main函数位于base_dir/tools/driver/driver.cpp文件中
使用n单步运行,到了
340 bool ClangCLMode = false;
(gdb)
341 if (TargetAndMode.second == "--driver-mode=cl" ||
的时候,从名字上看这个条件if语句用来判断是否是ClangCLMode。我们已知CL是Windows上的标准C++编译器,而clang是一个多端开源编译器,同样支持Windows平台,因此这一步是判断环境是否为windows的CL环境
然后一直没有执行程序块,猜想正确
然后继续单步调试到
374 if (FirstArg != argv.end() && StringRef(*FirstArg).startswith("-cc1")) {
(gdb)
383 bool CanonicalPrefixes = true;
374行是判断是否有一个参数是-cc1,这个很明显是一个gcc中常用的参数,在编译文件的时候,很多时候是一个默认的参数,这里的if判断应该为真才对。在使用clang++直接编译普通cpp文件的时候,确实为真,但是,在这里,比较奇怪的是,程序块并没有执行,条件为假,这个是调试过程中一个很奇怪的地方。忽略这里的问题,继续向下。
(gdb)
456 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(argv));
这里出现了第一个关键function,这个function使用我们设置的args建立了Compilation,我们做的就是编译器的源码分析,因此这里应该是一个关键的部分。s进入这个函数。
Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) 函数头是这样的,位于base_dir/lib/Driver/driver.cpp中。
简略的扫一遍代码,发现,里边先是进行了InputArg的解析,然后根据这些args进行分析到底是采用了什么编译参数。
单步到
// Perform the default argument translations.
DerivedArgList *TranslatedArgs = TranslateInputArgs(*UArgs);
// Owned by the host.
const ToolChain &TC = getToolChain(
*UArgs, computeTargetTriple(*this, DefaultTargetTriple, *UArgs));
// The compilation takes ownership of Args.
Compilation *C = new Compilation(*this, TC, UArgs.release(), TranslatedArgs);
按照注释中的意思,这里解析完了参数,建立了所有hostDevice上的Compilation,如果需要关注host端到底解析到了什么参数,需要关注这之前的代码
继续单步向下,遇到
// Populate the tool chains for the offloading devices, if any.
CreateOffloadingDeviceToolChains(*C, Inputs);
这个function从注释来看,应该是建立从设备(slave device或者说offloading device)
跟进去这个函数,发现函数头是
void Driver::CreateOffloadingDeviceToolChains(Compilation &C, InputList &Inputs)
// We need to generate a CUDA toolchain if any of the inputs has a CUDA type.
从中间的代码也可以清晰的发现,这里是建立NVIDIA CUDA代码选项的函数
llvm::Triple CudaTriple(HostTriple.isArch64Bit() ? "nvptx64-nvidia-cuda" : "nvptx-nvidia-cuda");
这个函数退出后,回到BuildCompilation中
// Construct the list of abstract actions to perform for this compilation. On
// MachO targets this uses the driver-driver and universal actions.
if (TC.getTriple().isOSBinFormatMachO())
BuildUniversalActions(*C, C->getDefaultToolChain(), Inputs);
else
BuildActions(*C, C->getArgs(), Inputs, C->getActions());
根据注释中的内容,这个地方对linux的代码生成(之前已经判断出了windows代码的生成,如果是windows上代码的生成,不会走到这里),进行了一个区分,将其分成了普通linux代码和macOS,其中macOS 上,使用BuildUniversalActions函数建立Actions
执行完该函数,返回到main函数
457 int Res = 0;
(gdb)
458 SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
(gdb)
459 if (C.get())
(gdb)
460 Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
从函数名字上看这里应该是执行了编译过程,跟进去这个函数看一下。
int Driver::ExecuteCompilation(
Compilation &C,
SmallVectorImpl<std::pair<int, const Command *>> &FailingCommands) {
// Just print if -### was present.
if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) {
C.getJobs().Print(llvm::errs(), "\n", true);
return 0;
}
// If there were errors building the compilation, quit now.
if (Diags.hasErrorOccurred())
return 1;
// Set up response file names for each command, if necessary
for (auto &Job : C.getJobs())
setUpResponseFiles(C, Job);
C.ExecuteJobs(C.getJobs(), FailingCommands);
// Remove temp files.
- CleanupFileList(C.getTempFiles());
// If the command succeeded, we are done.
if (FailingCommands.empty())
return 0;
从函数体上看,先获取参数,之后执行Jobs,然后清理临时文件,是一个非常正常的流程,奇怪的是,执行到这里后if (FailingCommands.empty()),条件为真,判断失败的FailingCommands是否为空后,就直接返回到main函数了,中间经历了比较长的过程,如果没有设置set follow-fork-mode child这个选项,就一直没法进入真正的编译过程,在之前的了解中,clang中应该有一个parseAST()的函数用来生成AST树,但是如果不加set follow-fork-mode child这个选项,就是无法到达这里,即使在这里添加断点也无效。这个问题是使用gdb调试clang编译CUDA源码的主要难点。
set follow-fork-mode child是gdb中的一个选项,主要用法是
set follow-fork-mode [parent|child] 用来调试多线程程序中,当子线程建立时,是留在父进程还是进入子进程。在默认情况下,一直留在父进程,我们的调试过程中,就会导致程序无法真正的进入编译的进程中,导致调试失败。可以说,clang编译CUDA程序的时候,先建立编译器,然后再执行编译器的时候,是通过fork子进程的方式来新启动一个编译器的。
跟进去子进程,重新进入main函数。
这次又将参数解析了一遍,然后运行到了
376 if (MarkEOLs) {
(gdb)
380 return ExecuteCC1Tool(argv, argv[1] + 4);
这次运行到了ExecuteCC1Tool这个函数中,这就和我们之前想象的一样了
之后进入了cc1_main这个函数中,此函数位于base_dir/tools/driver/cc1_main.cpp中
一进来就是新建一个编译实例
173 std::unique_ptr<CompilerInstance> Clang(new CompilerInstance());
从这里来看,实例化了一个CompilerInstance,这个CompilerInstance是整个编译器中主要的成员,其类图如下所示:
从代码上看,在
197 bool Success = CompilerInvocation::CreateFromArgs(
绑定了调用过程,在
221 Success = ExecuteCompilerInvocation(Clang.get());
执行了编译器的调用过程,ExecuteCompilerInvocation函数属于clang类中,这个类位于base_dir\lib\FrontendTool\ExecuteCompilerInvocation.cpp中,然后在
246 std::unique_ptr<FrontendAction> Act(CreateFrontendAction(*Clang));
中创建了FrontedAction,在后边的
249 bool Success = Clang->ExecuteAction(*Act);
执行了FrontendAction,进入了base_dir/lib/Frontend/CompilerInstance.cpp中的bool CompilerInstance::ExecuteAction(FrontendAction &Act)中,在执行到
914 if (getLangOpts().CUDA && !getFrontendOpts().AuxTriple.empty()) {
(gdb) p getLangOpts().CUDA
$1 = 1
这里,我们可以肯定的说,编译器的编译选项中是将其当做CUDA程序来编译,继续执行
同时,在917行的地方,同样验证了我们的猜想
917 TO->HostTriple = getTarget().getTriple().str();
(gdb) p getTarget().getTriple().str()
$3 = "nvptx64-nvidia-cuda"
之后执行到line946开始做真正的源码的分析等工作
for (const FrontendInputFile &FIF : getFrontendOpts().Inputs) {
// Reset the ID tables if we are reusing the SourceManager and parsing
// regular files.
if (hasSourceManager() && !Act.isModelParsingAction())
getSourceManager().clearIDTables();
if (Act.BeginSourceFile(*this, FIF)) {
Act.Execute();
Act.EndSourceFile();
}
}
前边的应该都没有做太多的工作,主要的应该在Act相关的BeginSourceFile、Execute、EndSourceFile这三个函数内
其中BeginSourceFile函数内判断了输入是AST树还是源码,这里的输入是IK_CUDA
204 if (Input.getKind() == IK_AST) {
(gdb) p Input.getKind()
$1 = clang::IK_CUDA
后边完成的是setVirtualFileSystem、createFileManager、createSourceManager、createPreprocessor、createASTContext、CreateWrappedASTConsumer
尤其是在CreateWrappedASTConsumer中,这里边对前端Frontend的Plugin进行了检测,如果检测到了Plugin,需要进行Action的添加,这些Action被叫做AfterConsumers。
现在执行结束这个函数,返回到bool CompilerInstance::ExecuteAction中
if (Act.BeginSourceFile(*this, FIF)) {
Act.Execute();
Act.EndSourceFile();
}
进入Execute中,函数代码位于base_dir/lib/Frontend/FrontendAction.cpp中,函数原型是
void ASTFrontendAction::ExecuteAction(),这个里边最重要的就是最后ParseAST( CI.getSema(), CI.getFrontendOpts().ShowStats,CI.getFrontendOpts().SkipFunctionBodies);
这里就是建立AST树的地方
之后的EndSourceFile负责EndSourceFileAction和clearOutputFiles
Clang调试CUDA代码的更多相关文章
- jdb调试scala代码的简单介绍
在linux调试C/C++的代码需要通过gdb,调试java代码呢?那就需要用到jdb工具了.关于jdb的用法在网上大家都可以找到相应的文章,但是对scala进行调试的就比较少了.其实调试的大致流程都 ...
- Firebug调试js代码
Firebug功能异常强大,不仅可以调试DOM,CSS,还可以调试JS代码,下面介绍一下调试JS. 1.认识console对象 console对象是Firebug内置的对象,该对象可以在代码中写入,可 ...
- 远程debug调试java代码
远程debug调试java代码 日常环境和预发环境遇到问题时,可以用远程调试的方法本地打断点,在本地调试.生产环境由于网络隔离和系统稳定性考虑,不能进行远程代码调试. 整体过程是通过修改远程服务JAV ...
- 3分钟干货学会使用node-inspector调试NodeJS代码
使用node-inspector调试NodeJS代码 任何一门完备的语言技术栈都少不了健壮的调试工具,对于NodeJS平台同样如此,笔者研究了几种调试NodeJS代码的方式,通过对比,还是觉得node ...
- NotePad++ 调试PHP代码中文显示乱码
最近在NotePad++上调试PHP代码,按照示例代码进行调试,结果在显示中文的时候显示一堆乱码,于是上网百度,有2种方法可以解决: 按调试方式有2种方法: 1.菜单插件-NppExec: " ...
- CUDA代码的高亮设置
以下基于"WIN7(64位)+Visual Studio 2010+CUDA7.5". 语法高亮除了看起来舒服之外,还可以使用F11寻找函数.变量定义,输入函数的时候也会有相应的提 ...
- 利用chrome调试JavaScript代码
看见网上很多人问怎么用chrome调试JavaScript代码,我也对这个问题抱着疑问,但是没有找到一篇能用的中文文章(可能我的google有问题),也不知道怎么点出一篇E文的,感觉作者写得不错,所以 ...
- 20145311利用gdb调试汇编代码
利用GDB调试汇编代码 首先编写c语言原代码,我使用的是同学分析过的代码 #include<stdio.h>short addend1 = 1;static int addend2 = 2 ...
- windbg调试C#代码(一)
用windbg调试C#代码是比较麻烦的,因为windbg是针对OS层级的,而C#被CLR隔了一层,很多原生的命令如查看局部变量dv.查看变量类型dt等在CLR的环境中都不能用了.必须使用针对CLR的扩 ...
随机推荐
- jxbrowser java代码直接调用js代码
https://blog.csdn.net/shuaizai88/article/details/73743669 final Browser browser = new Browser(); Bro ...
- js设计模式-代理模式
1.什么是设计模式? 设计模式:在软件设计过程中常用的代码规范,针对特定的场景 2.应用场景: 麦当劳点餐 观察者模式 规定的代码格式 花店送花 :代理模式 真实对象(男同学)-----代理对 ...
- Node JS复制文件
/** * Created by Administrator on 2019/11/6. *指尖敲打着世界 ----一个阳光而又不失帅气的少年!!!. */ var fs=require(" ...
- 文笔很差系列4 - Kris Kremo
转载请标注原链接 https://www.cnblogs.com/xczyd/p/11127671.html Kris Kremo老先生(1951年出生,1970年第一次正式登台,截止2019年练习时 ...
- javascript之Number对象
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- HashMap 的实现原理解析(转载)
HashMap 概述 HashMap 是基于哈希表的 Map 接口的非同步实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.此类不保证映射的顺序,特别是它不保证该顺序恒久不 ...
- 2019.11.10【每天学点SAP小知识】Day3 - ABAP 7.40新语法 值转化和值赋值
1.语法为 CONV dTYPE|#(...)\ # 代表任意类型 "7.40之前表达式 . DATA helper TYPE string. DATA xstr TYPE xstring. ...
- 修改deploy location
在MyEclipse,如果某Web Project重命名后,项目名称有可能仍然是之前的名称. 修改路径: 第一, 路径:右击某Web Project,Properties->MyEclipse- ...
- BOM Summary P268-P269
The Browser Object Model(BOM) is based on the window object, which represents the browser window and ...
- Autofac实现AOP拦截
本文主要是详解一下在ASP.NET Core中,采用替换后的Autofac来实现AOP拦截. Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题.AO ...