Emscripten教程之如何调试代码(六)

翻译:云荒杯倾
本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏。
也能够去做者的博客阅读文章。
欢迎加入Wasm和emscripten技术交流群,群聊号码:939206522。html

调试Emscripten代码的主要优势之一是,源代码既能够在本地平台上进行调试,也可使用web浏览器日益强大的工具集——包括调试器、分析器和其余工具。c++

Emscripten提供了许多帮助调试的功能和工具:git

  • 编译器调试信息flags,容许您在已编译的代码中保存调试信息,甚至建立源映射,以便在浏览器中调试时能够单步调试c++源代码。
  • 调试模式,它产生调试日志和存储 编译时产生的中间文件 进行分析。
  • 编译器设置,使运行时检查内存访问和公共分配错误。
  • 还支持手动打印调试emscripten生成代码,这在某些方面甚至比本地平台工做效果更好。
  • 自动调试器,它会自动地使用LLVM的中间代码写到内存。

本文描述了由Emscripten提供的用于调试的主要工具和设置,以及如何调试一些Emscripten特有的问题。github

调试信息

默认下,若是是优化编译,Emcc会删除大部分调试信息。Optimisation级-01和以上删除LLVM调试信息,也禁用了运行时断言检查。优化级别-02以上,代码被压缩编译器改编,变得几乎不可读。web

emcc -g标志可用于在编译的输出中保存调试信息。默认状况下,此选项保护空白、函数名和变量名。segmentfault

你可使用五个级别中的一个来指定标记:-g0、-g一、-g二、-g3和-g4。每一个级别都在最后编译,以在编译后的输出中逐步提供更多的调试信息。g3标志提供与-g标志相同级别的调试信息。浏览器

g4选项提供了最多的调试信息—--—它生成了源映射(source map),容许您在Firefox、Chrome或Safari浏览器的调试器中查看和调试C/C++源代码。安全

note:
当你既用调试flag又用优化flag时,有些优化可能会被禁掉,好比,若是你使用-O3 -g4 编译,为了给你提供足够多的调试信息,有一些-O3的优化就得禁用掉。

调试模式(EMCC_DEBUG)

EMCC_DEBUG环境变量能够用来设置启用/不启用Emscripten的调试模式:函数

# Linux or Mac OS X
    EMCC_DEBUG=1 ./emcc tests/hello_world.cpp -o hello.html

    # Windows
    set EMCC_DEBUG=1
    emcc tests/hello_world.cpp -o hello.html
    set EMCC_DEBUG=0

使用EMCC_DEBUG = 1设置,emcc会产生调试输出文件,并为编译器的各个编译阶段生成中间文件。
EMCC_DEBUG= 2还为每趟JavaScript优化器遍历(pass)生成中间文件。工具

调试日志和中间文件输出到TEMP_DIR/emscripten_temp,其中TEMP_DIR默认在/tmp(/tmp的位置在.emscripten配置文件定义)。

能够对调试日志进行分析,以对每一个步骤中所作的更改进行分析和检查。

编译器设置

Emscripten有许多能够用于调试的编译器设置。使用emcc -s选项选择这些设置,他们将覆盖任何优化标志。例如:

./emcc -01 -s ASSERTIONS=1 tests/hello_world

最重要的设置是:

  • ASSERTIONS=1 用于为内存分配错误启用运行时检查(例如,写入比分配更多的内存)。它还定义了Emscripten如何处理程序流中的错误。能够将值设置为ASSERTIONS=2,以便运行额外的测试。
    不优化编译时,ASSERTIONS=1是默认开启的。对于优化编译的代码(-01和以上级别)它是关闭的。
  • SAFE_HEAP= 1增长了额外的内存访问检查,并将为诸如非内联化0(dereferencing 0)和内存对齐等问题提供清晰的错误。你也能够设置SAFE_HEAP_LOG以打印SAFE_HEAP操做。
  • 经过STACK_OVERFLOW_CHECK =1 标记在堆栈的末尾添加一个运行时的令牌值,令牌值会在某些位置被检查,以验证用户代码是否意外地写出了堆栈的末尾。虽然溢出Emscripten堆栈不是一个安全问题(JavaScript已经被沙箱化了),但写出堆栈将会致使全局数据内存损坏和Emscripten堆中动态分配的内存碎片化,这使得应用程序以意想不到的方式失败。值STACK_OVERFLOW_CHECK = 2启用了更详细的堆栈保护检查,它以牺牲一些性能的代价提供更精确的callstack。若是ASSERTIONS= 1,STACK_OVERFLOW_CHECK默认值为2,ASSERTIONS为其余值时STACK_OVERFLOW_CHECK默认不启用。

src/settings.js中定义了许多其余有用的调试设置。有关更多信息,请搜索“check”和“debug”关键字的文件。

emcc详细输出

用emcc -v选项编译,将-v传递给LLVM,而后在工具链上运行Emscripten的内部完整性检查。

verbose模式还能启动Emscripten的调试模式(EMCC_DEBUG)以生成编译器的各个阶段的中间文件。

手动打印调试

您还能够用printf()语句手工编写源代码,而后编译并运行代码来研究问题。

若是你对问题行有很好的了解,你能够在JavaScript添加print(新的Error().stack)代码,以获得堆栈跟踪。另外还有stackTrace(),它发出堆栈跟踪,并尝试使用c++的去除改编的函数名(若是你不想或者不须要让c++ 函数名去除改编,你能够调用jsStackTrace())。

调试打印输出甚至能够执行任意的JavaScript。例如:

function _addAndPrint($left, $right) {
            $left = $left | 0;
            $right = $right | 0;
            //---
            if ($left < $right) console.log('l<r at ' + stackTrace());
            //---
            _printAnInteger($left + $right | 0);
    }

禁止优化

有时候,编译的时候,禁用LLVM优化(llvm-opts)或禁用JavaScript优化(js-opts)是颇有用的。

好比说,如下命令即容许调试信息又使用-O2优化(既llvm和js都优化),可是又明显关闭了js的优化器。

./emcc -O2 --js-opts 0 -g4 tests/hello_world_loop.cpp

这样就能产生相对于llvm优化的代码来讲更易调试的js代码:

function _main() {
            var label = 0;
            var $puts=_puts(((8)|0)); //@line 4 "tests/hello_world.c"
            return 1; //@line 5 "tests/hello_world.c"
    }

Emscripten特有问题

内存对齐问题

Emscripten内存表示假定加载和存储是对齐的。在未对齐的地址上执行正常的加载或存储可能会失败。

SAFE_HEAP能够用来显示内存对齐问题。

通常来讲,最好避免不对齐的读写-----他们一般是因为未定义的行为致使的。然而,在某些状况下,它们是不可避免的—----例如,若是要移植的代码从一些预先存在的数据格式的打包结构(packed structure)中读取int。

Emscripten支持未对齐的读写,但它们要慢得多,并且必须在绝对必要时使用。执行一个不对齐的读或写你能够:

  • 手动读取单个字节并从新构造所有值
  • 使用emscripten_align*类型,它定义了基本类型的不对齐版本(short,int,float,double)。这些类型的全部操做都是不彻底对齐的(在大多数状况下使用1个variants,这意味着没有任何对齐)。

函数指针问题

若是你的函数指针调用获得一个abort(),那么问题是在调用时,没有在预期的函数指针表中找到这个函数指针。

note:
nullFunc是函数指针表中用于填充空索引的函数(b0和b1是优化编译下它的别名)。指向无效索引的函数指针会调用这个函数,而后调用abort().

有几个可能的缘由:

  • 您的代码调用了一个从另外一个类型转换来的函数指针(这是未定义的行为,但它确实发生在真实的代码中)。在优化的Emscripten输出中,每一个函数指针类型都基于它的原始签名,存储在一个单独的表中,所以您必须调用具备相同签名的函数指针以得到正确的行为(更多信息参见代码可移植性部分中的函数指针问题)。
  • 您的代码在空指针或者dereferencing 0上调用方法。这种bug能够由任何类型的编码错误引发,但表现为函数指针错误,由于在运行时的预期表中没法找到函数。

为了调试这些问题:

  • 使用-Werror编译。这就把警告变成了错误,这些错误可能有用,由于一些未定义行为的状况会显示警告。
  • 使用-s ASSERTIONS=2,获得一些 关于被调用的函数指针和它的类型 有用的信息。
  • 查看浏览器堆栈跟踪,查看错误发生的地方以及应该调用哪一个函数。
  • 使用SAFE_HEAP=1和禁用函数指针别名(aliasing_function_pointer = 0)编译。这使得错误类型的函数指针 不可能在不引发错误的状况下 调用,换句话说就是这样编译会使 错误类型的函数指针调用 必定会报错。调用命令:-s SAFE_HEAP=1 -s aliasing_function_pointer =0

aliasing_function_pointer = 0也颇有用,由于它确保调用错误表中的指针地址会致使明显的错误。若是没有这样的设置,这样的调用只执行地址上的任何函数,这将很难进行调试。

死循环

无限循环致使您的页面挂起。在一段时间以后,浏览器将通知用户该页面被卡住并提供中止或关闭它的选择。

若是您的代码中有无限循环,那么找到问题代码的一个简单方法就是使用JavaScript profiler。在Firefox profiler中,若是代码进入无限循环,您将看到在profiler的末尾有一块代码重复执行相同的操做。

AutoDebugger

警告:
这个选项主要为Emscripten核心开发者提供使用。

Emscripten代码移植系列文章

Emscripten代码移植主题系列文章是emscripten中文站点的一部份内容。
本文是第三个主题第二篇文章。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍链接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
第六个主题介绍Emscripten如何调试代码

相关文章
相关标签/搜索