9

我已经构建了一个 LLVM 目标前端,它可以产生一些 IR。随后,完全可以预料,IR 输出在某些情况下是不正确的(例如,它看起来是正确的,但结果程序在执行时会崩溃)。但是,我还没有找到很多有用的工具来处理这个问题。

我曾尝试使用 lli,但错误消息输出非常无用(当您假设解释器可以提供非常精确的错误详细信息时)。

我研究了将 IR 转换为 C 代码,然后使用 Visual Studio 对其进行调试,但似乎此功能已从 LLVM 中删除。

我还研究了与 GDB 打交道。但是,DWARF 调试信息格式似乎对一些现有的语言非常特定,此外,我用前端翻译的源是正确的,它是生成的 IR 是错误的,所以原始源的调试符号不会'不要太有帮助-例如,我需要查看一堆中间寄存器值的值,这些值不对应于任何源变量,或者编译器生成的函数中的断点。

有哪些工具和技术可用于调试 LLVM IR 输出?

4

2 回答 2

5

我不确定我是否完全理解您的问题。您是说您的编译器(从语言 X 到 LLVM IR)产生了不正确的输出(不正确的 LLVM IR)并且您不确定如何调试它?换句话说,有两种可能:

  1. 你的编译器生成的 IR 不正确——你可以指着一些指令说——这不是我想要生成的。
  2. IR 看起来是正确的,但没有产生我期望它产生的结果。

我假设它是(1)你在谈论(因为这是问题所说的,在你更新它之前)

那么,这将不是特定于 LLVM 的问题。假设您正在编写从语言 X 到本机代码的编译器。生成的本机代码不正确 - 您如何调试问题?好吧,你调试你的编译器,很明显。您尝试找到编译器对输入的理解正确的最后一个位置,或者它变得不正确的第一个位置。你如何做到这一点在很大程度上取决于你的编译器的体系结构。但是,有很大帮助的是在编译器中拥有其他中间层的可打印表示。

例如,Clang(从 C、C++ 和 Objective C 生成 LLVM IR)可以转储其完整的 AST。因此,查看 AST 中的错误代码可以将编译器减半,有助于确定问题出在前端(C 源 -> AST)还是代码生成(AST -> LLVM IR)。LLVM 后端(将 LLVM IR 编译为本机代码)也有一些中间层(最显着的是 SelectionDAG 和 MI),可以为了调试而对其进行检查。这些只是其他现有编译器的示例,YMMV 和你的。

于 2013-05-29T15:31:27.043 回答
2

Will Diez 描述了他是如何实现的:
https ://groups.google.com/d/msg/llvm-dev/O4Dj9FW1gtM/ovnm6dqoJJsJ

大家好,

出于我自己的目的,我写了一个完全符合你们所描述的过程:将调试元数据添加到 LLVM IR。

作为通行证,它必须解决“这个文件需要存在于磁盘上的某个地方以便 gdb 可以找到它”的问题,我解决了我将它转储到某个地方的 /tmp/ 的问题。不是一个很好的解决方案(谁删除了这些?)但效果很好。

另一个有趣的问题是如何与任何现有的调试元数据共存,这对于同时调试与 C 源代码内联的 IR 转换以用于诸如 SAFECode、ASan/TSan 之类的仪表式通行证很有用。

快速示例:

(gdb) break main
Breakpoint 1 at 0x4010b1: file
/home/wdietz2/magic/test/unit/test_loop.c, line 9.
(gdb) r
Starting program:
/home/wdietz2/llvm/32-obj-make/projects/magic/test/Output/test_loop

Breakpoint 1, main (argc=<value optimized out>, argv=<value optimized
out>) at /home/wdietz2/magic/test/unit/test_loop.c:9
9         unsigned k = 0;
Missing separate debuginfos, use: debuginfo-install
glibc-2.12-1.80.el6_3.5.x86_64 libgcc-4.4.6-4.el6.x86_64
libstdc++-4.4.6-4.el6.x86_64
(gdb) n
10        source(argc != 0, &k);
(gdb) n
14        %and.i.i.i.i104 = and i64 %4, 70368744177660
(gdb) n
15        %5 = load i8** @global, align 8
(gdb) n
18        store i32 16843009, i32* %6, align 1
(gdb) n
19        store i8 1, i8* getelementptr inbounds ([1 x i8]* @array,
i64 0, i64 0), align 1
(gdb) n
20        call coldcc void @runtime_func() nounwind
(gdb) n
11        while(i-- > argc)
(gdb) n
23        %and.i.i.i.i85 = and i64 %7, 70368744177660
(gdb) n
14          while(j++ < i) k += j;
(gdb) n
11        while(i-- > argc)
(gdb) n
14          while(j++ < i) k += j;
(gdb) n
102       %77 = load i8** @global, align 8
(gdb) n
105       %79 = load i32* %78, align 4
(gdb) n
106       %cmp7.i.i.i = icmp ne i32 %79, 0
(gdb) n
108       call void @llvm.memset.p0i8.i64(i8* %add.ptr.i.i.i.i86, i8
%conv8.i.i.i, i64 4, i32 1, i1 false) nounwind
(gdb) n
14          while(j++ < i) k += j;
(gdb) n
15          while(j-- > 0) k *= k + j;
(gdb) n
95        %69 = load i8** @global, align 8
(gdb) n
98        %71 = load i32* %70, align 4
(gdb)

pass 本身相当简单——它解决的难题是将 IR 发送到磁盘并推断指令 * 在哪一行,如果在 LLVM 中正确完成,这真的不应该成为问题。如果需要,我当然可以根据要求提供代码。

简而言之,它对我来说似乎很有效,并且在 LLVM 本身中正确完成它会很棒!

不幸的是,该代码似乎不可用。

于 2017-03-23T14:37:55.590 回答