FFmpeg原生脚本构建windows debug版的调试跟踪问题

这次要记录的感觉是个比较硬核的问题,限于标题字数限制,没法准确表达出所有问题,比如,调试断点单步跟踪FFmpeg代码时,代码不按顺序执行,满处乱飞,断点位置查看周围局部变量,不能正常显示值,比如这样:

局部变量offset无法显示当前值,而是显示“Variable is optimized away and not available.”的信息,看字面意思可以猜到,代码被编译器做了优化了,比如直接内联省去中间变量,所以看不到了。

但是,FFmpeg的构建configure参数确实设置了

–disable-optimizations –enable-debug –disable-stripping

这些官方说明的构建debug版的参数,怎么实际调试的时候还是会出现上面描述的种种看起来是代码被优化了造成的调试跟踪障碍呢?

首先,说下我的windows版FFmpeg构建方式,这个其实方法有很多,没记错的话,官方推荐的是mingw、cygwin这些windows移植的*nix环境,以让FFmpeg开源项目中的各种构建脚本顺利执行,最终编译链接。还有github上有人大费周章的专门为某些版本的FFmpeg做了Visual Studio某个版本的工程(这种方法理论上不会出现本文描述的问题)。而博主我用的是Windows WSL的方式,感觉比mingw、cygwin干净些,同时又不会有VS解决方案方式那么固定某个版本的限制,具体操作方法网上有不少说明,需要的可以参考下,不在本文讨论范围内。

言归正传,之所以会导致configure开启了debug,却依然触发编译器优化代码的原因在FFmpeg configure脚本的这里:

    elif $_cc -nologo- 2>&1 | grep -q Microsoft || { $_cc -v 2>&1 | grep -q clang && $_cc -? > /dev/null 2>&1; }; then
        _type=msvc
        if $_cc -nologo- 2>&1 | grep -q Microsoft; then
            _ident=$($_cc 2>&1 | head -n1 | tr -d '\r')
        else
            _ident=$($_cc --version 2>/dev/null | head -n1 | tr -d '\r')
        fi
        _DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk '\''/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }'\'' > $(@:.o=.d)'
        _DEPFLAGS='$(CPPFLAGS) $(CFLAGS) -showIncludes -Zs'
        _cflags_speed="-O2"
        _cflags_size="-O1"
        _cflags_noopt="-O1"
        if $_cc -nologo- 2>&1 | grep -q Linker; then
            _ld_o='-out:$@'
        else
            _ld_o='-Fe$@'
        fi
        _cc_o='-Fo$@'
        _cc_e='-P -Fi$@'
        _flags_filter=msvc_flags
        _ld_lib='lib%.a'
        _ld_path='-libpath:'
        _flags='-nologo'
        disable stripping

可以看到,构建脚本中针对msvc编译器的传参配置里,noopt(也就是debug版状态)的cflags传递的是-O1,而不是正经Debug版的-Od!

其实,在定位到这里之前,我还已经尝试过在configure时,通过“–extra-cflags”,传递-Od参数的方式,但由于configure脚本处理这个参数的结果是放在_cflags_noopt前的,也就是最终调用cl.exe时,先传了-Od,后面又会补上一个-O1,造成编译时提示:

warning D9025 : overriding ‘/Od’ with ‘/O1’

最终还是O1生效!那么,直接把configure脚本里的msvc部分的O1改成Od不就完了么?我一开始也是这么想的,但是改完发现最后链接avcodec动态库时报了下面的错:

LD libavcodec/avcodec-60.dll
Creating library libavcodec/avcodec.lib and object libavcodec/avcodec.exp
fdctdsp_init.o : error LNK2019: unresolved external symbol ff_fdct_sse2 referenced in function ff_fdctdsp_init_x86
vc1dsp_init.o : error LNK2019: unresolved external symbol ff_vc1dsp_init_mmx referenced in function ff_vc1dsp_init_x86
vc1dsp_init.o : error LNK2019: unresolved external symbol ff_vc1dsp_init_mmxext referenced in function ff_vc1dsp_init_x86
libavcodec\avcodec-60.dll : fatal error LNK1120: 3 unresolved externals
ffbuild/library.mak:118: recipe for target 'libavcodec/avcodec-60.dll' failed
make: *** [libavcodec/avcodec-60.dll] Error 96

提示的错误是几个FFmpeg内置dsp算法的x86 simd优化实现函数,按说这个功能会被configure正确配置,如果支持的话,就编译链接,不支持的话init_x86也不会被调用。目前这个报错的情况表明init_x86部分的代码,认为支持这个功能,所以去调用init去初始化相关simd优化版本的dsp算法实现,但是对应的x86优化dsp实现代码里又认为不支持,直接没有编译导出相关函数,这点我后来用objdump -t查看了下相关的.o文件,发现确实没有导出链接报错的几个函数符号。

后来回退了configure里的-Od修改,clean后重新编译就没这问题,于是判断应该是FFmpeg的构建脚本不能直接用-Od编译,会造成部分代码编译链接错误,这点看configure脚本上面icl部分有个注释:

        _cflags_size="-O1 -Oi" # -O1 without -Oi miscompiles stuff

参数搭配不对就会miscompile,em…好吧,于是我放弃了这种方法,还是老实用-O1了。

那么,就没办法痛快准确的调试跟踪windows版FFmpeg代码了么?经过不断摸索,我发现了一个不太聪明,但确实有效的方法:使用msvc的#pragma源码内编译链接配置,具体操作方式如下:

在需要无编译器优化代码的FFmpeg源码文件最开头加入#pragma optimize(“”, off),最后一行加入#pragma optimize(“”, on),用这种低能一点的方法,完全屏蔽本源码文件的所有编译器优化操作。

这时最终编译出的库也好,执行程序也好,在用VS直接调试时就不会再出现本文开头描述的问题了,仿佛和原生VS解决方案,以及原生cmake项目的debug编译调试一样的效果了。唯一比较恶心的地方就是需要调试哪个源码,哪个就得收尾加#pragma,不过谁让-Od编译会出错呢,有总比无强吧,后面有更好的方法能把有变为优再说。

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v3<<处理后发布,原文只有博主可以看到。