PPRTC要点正能量

WebRTC Android的调试

WebRTC在Mac/iOS、Windows和Linux上的调试都是很方便的,能跟到源代码中,在Android上的调试就呵呵了,挺麻烦的。
大概有两种方法,一种是有人辛苦写个cmake文件做个Android Studio的工程,但容易跟不上WebRTC的更新。另一种是还是最好用gn和ninja来编译,绑定源码的方式来调试生成的apk。

不管是哪种方式,我们大概可以想一下–原理。

首先,这显然是远程调试,我们编译的机器是Linux,目标是Android。
远程调试,lldb/gdb都是支持的,大概是:在Remote(目标)机器上启动一个如lldb-server这样的,然后这个server能加载(或者attach到要调试的进程/apk);在本地编译机器上有启动lldb,连接到远程的server,通过调试协议传输调试信息和命令。
不管是Android Studio还是其他IDE,要调试Android实际上都是上面这个过程。只是Android的权限管理比较严格,所以要启动一个lldb-server也是不容易的。通常使用如下类似步骤和命令。

1. 想办法把lldb-server搞到机器上,注意lldb-server本身也是arm,arm64...相关的,不要搞错版本。
   这步不深究了,我是采用Android Studio创建“Profile or Debug APK”工程,然后安装上去的,默认Android Studio已经帮我拷贝上去了。
   并放在如/data/local/tmp/lldb-server目录下(再强调一下,注意这个是不是和调试目标匹配)
2. 执行run-as命令放到/data/data/<apk-package-name>/下,并且启动,下面两个是Android Studio打印出来的命令行;主要是保证权限问题
   $ adb shell cat /data/local/tmp/lldb-server | run-as org.webrtc.examples.androidnativeapi sh -c 'cat > /data/data/org.webrtc.examples.androidnativeapi/lldb/bin/lldb-server && chmod 700 /data/data/org.webrtc.examples.androidnativeapi/lldb/bin/lldb-server'
   $ adb shell cat /data/local/tmp/start_lldb_server.sh | run-as org.webrtc.examples.androidnativeapi sh -c 'cat > /data/data/org.webrtc.examples.androidnativeapi/lldb/bin/start_lldb_server.sh && chmod 700 /data/data/org.webrtc.examples.androidnativeapi/lldb/bin/start_lldb_server.sh'
3. 可以cat 一下 start_lldb_server.sh,就是传几个参数来启动lldb-server
   Starting LLDB server: /data/data/org.webrtc.examples.androidnativeapi/lldb/bin/start_lldb_server.sh /data/data/org.webrtc.examples.androidnativeapi/lldb unix-abstract /org.webrtc.examples.androidnativeapi-0 platform-1598252686634.sock "lldb process:gdb-remote packets"
   展开后大概类似于就是
   ./adb shell "run-as org.webrtc.examples.androidnativeapi sh -c '/data/data/org.webrtc.examples.androidnativeapi/lldb/bin/lldb-server platform --server --listen unix-abstract:///data/data/org.webrtc.examples.androidnativeapi/debug.socket --log-file /data/data/org.webrtc.examples.androidnativeapi/lldb/log/lldb-server.log --log-channels "lldb process:gdb-remote packets" </dev/null >/data/data/org.webrtc.examples.androidnativeapi/lldb/log/platform-stdout.log 2>&1'"

关键几点讲一讲:

  1. unix-abstract: 就是开个unix的通讯socket。 实际上是会在本地和远端通过adb系统的forward方式传输调试数据和命令,包括usb传输数据。
  2. 在本地是通过lldb命令,来调试的,在connect命令时候,我相信是调用了adb的命令(通过usb传输数据)建立管道真机
    $lldb
    (lldb)platform list
    (lldb)platform select remote-android
    (lldb)platform connect unix-abstract-connect:///data/data/org.webrtc.examples.androidnativeapi/debug.socket
  3. 我们用Android Studio的话,这些都隐藏自动做掉了

明白了上面的原理,我们就可以开始Android的编译和调试了。
我们的目标是通过gn编译出APK,并在Android Studio中可以查看源码,设置断点调试。

Android Studio已经支持创建“Profile or Debug APK”工程。不多说,非常简单。

先设置要编译的目标,这里把symbol_level设置为2

cat out/Default64/args.gn
target_os = "android"
target_cpu = "arm64"
treat_warnings_as_errors = false
symbol_level = 2

默认编译出来APK example里面的so是strip掉了调试信息的,所以首先要避免调试信息被strip掉:


修改/home/terry/workspace/opensource/webrtc/webrtc-full/src/build/toolchain/android/BUILD.gn

template("android_clang_toolchain") {
  gcc_toolchain(target_name) {
    assert(defined(invoker.toolchain_args),
           "toolchain_args must be defined for android_clang_toolchain()")
    toolchain_args = invoker.toolchain_args
    toolchain_args.current_os = "android"

    # Output linker map files for binary size analysis.
    enable_linker_map = true

    _android_tool_prefix =
        "$android_toolchain_root/bin/${invoker.binary_prefix}-"

    # The tools should be run relative to the build dir.
    _tool_prefix = rebase_path("$_android_tool_prefix", root_build_dir)

    _prefix = rebase_path("$clang_base_path/bin", root_build_dir)
    cc = "$_prefix/clang"
    cxx = "$_prefix/clang++"
    ar = "$_prefix/llvm-ar"
    ld = cxx
    readelf = _tool_prefix + "readelf"
    nm = _tool_prefix + "nm"
    #print("*******************-----------------******************\n")
    #print(target_name)
    #ZLF - I commented following lines
    #下面这行strip = 是关键,有了它,在gcc_toolchain里面就会调用这个命令
    #strip = rebase_path("//buildtools/third_party/eu-strip/bin/eu-strip",
    #                    root_build_dir)
    #use_unstripped_as_runtime_outputs = android_unstripped_runtime_outputs

    # Don't use .cr.so for loadable_modules since they are always loaded via
    # absolute path.
    loadable_module_extension = ".so"
  }
}

这样之后,已经可以编译出一个带有调试信息的.so,并打包在apk里面。但是使用Android Studio调试时候,发现能够显示堆栈,但是无法定位源代码。

这是为什么呢?这个时候,我们就要想,调试程序显示源码的过程。呵呵:)
首先怀疑的是我们的.so是不是包含调试信息?这个可以有多个方法来确定,比如lldb,file命令加载后查看;我们这里采用objdump -W命令。

我们可以使用objdump(Android ndk下面不同的平台都有)打开一个.o(bj)文件

objdump -W '/home/terry/workspace/opensource/webrtc/webrtc-full/src/out/Default64/obj/rtc_base/synchronization/mutex/mutex.o'

.debug_line 节的调试内容转储:

  偏移:                       0x0
  长度:                      1117
  DWARF 版本:                4
  导言长度:        953
  最小指令长度:              1
  每个指令中最大操作码数:       1
  “is_stmt”的初始值:       1
  行基数:                      -5
  行范围:                      14
  操作码基数:                  13

 操作码:
  Opcode 1 has 0 args
  Opcode 2 has 1 arg
  Opcode 3 has 1 arg
  Opcode 4 has 1 arg
  Opcode 5 has 1 arg
  Opcode 6 has 0 args
  Opcode 7 has 0 args
  Opcode 8 has 0 args
  Opcode 9 has 1 arg
  Opcode 10 has 0 args
  Opcode 11 has 0 args
  Opcode 12 has 1 arg

 目录表 (偏移 0x1c):
  1	../../rtc_base
  2	../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include
  3	../../buildtools/third_party/libc++/trunk/include
  4	../../third_party/abseil-cpp/absl/base
  5	../../third_party/llvm-build/Release+Asserts/lib/clang/11.0.0/include
  6	../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/sys
  7	../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/bits
  8	../../third_party/abseil-cpp/absl/meta
  9	../../rtc_base/synchronization

 文件名表 (偏移 0x222):
  条目	目录	时间	大小	名称
  1	1	0	0	checks.h
  2	2	0	0	stdint.h
  3	3	0	0	atomic
  4	4	0	0	const_init.h
  5	3	0	0	type_traits
  6	5	0	0	stddef.h
  7	3	0	0	cstddef
  8	5	0	0	__stddef_max_align_t.h
  9	3	0	0	__nullptr
  10	3	0	0	stddef.h
  11	3	0	0	cstdint
  12	3	0	0	cstring
  13	2	0	0	string.h
  14	3	0	0	string.h
  15	3	0	0	cstdlib
  16	2	0	0	stdlib.h
  17	2	0	0	malloc.h
  18	3	0	0	math.h
  19	2	0	0	stdio.h
  20	3	0	0	cstdio
  21	6	0	0	types.h
  22	5	0	0	stdarg.h
  23	2	0	0	ctype.h
  24	3	0	0	cctype
  25	7	0	0	wctype.h
  26	3	0	0	cwctype
  27	7	0	0	mbstate_t.h
  28	3	0	0	cwchar
  29	2	0	0	wchar.h
  30	3	0	0	wchar.h
  31	8	0	0	type_traits.h
  32	9	0	0	mutex.cc
  33	9	0	0	mutex.h
  34	9	0	0	yield.h
  

我们看到 目录表 里面的路径是 “../..”,这个路径下,Android Studio应该是找不到这些信息的。或者把工程放在out/Default64这个输出目录下,也许可以,我没有试验。

lldb命令里面有一个setting,可以设置这个源码和调试信息不一致的情况,对于无法重新生成源代码的情况,可以使用。于是,我们尝试在Android Studio的“Run/Debug Configurations”设置中的“LLDB Post Attach Commands”里面添加

settings set target.source-map ./../../ /home/terry/workspace/opensource/webrtc/webrtc-full/src/

这个时候,重新启动调试,激动万分,发现Source代码正确定位并显示出来。

然后我们尝试在IDE里面下断点(不是lldb命令),却根本不能断点生效。What!
为什么呢???
搞了很久,终于受到lldb官方文档的启发,查看断点列表:

(lldb) breakpoint list --verbose 

发现确实如文档所说,Android Studio使用了绝对路径下的断点,这个和调试信息中的不匹配。
这样的话,没有办法了,只能通过重新编译,把调试信息中相对路径全部改为绝对路径,再试试了。

通过查找clang和gcc的参考说明,发现有一个选项“-fdebug-prefix-map=old=new”似乎是用于此。
找到一处可以修改编译选项的:src/build/config/compiler/BUILD.gn文件中

...

# Full symbols.
config("symbols") {
  if (is_win) {
    ...
  } else {
    ...

    if (!is_nacl || is_clang) {
      #--zlf changed: 链接时候不优化,保留变量调试信息
      cflags += [ "-g" ]   #-g2  
      #--zlf changed: 将映射地址修改为本机的全路径,方便在Android studio中的调试
      cflags += [ "-fdebug-prefix-map=../..=/home/terry/workspace/opensource/webrtc/webrtc-full/src" ]
      cflags += [ "-fdebug-prefix-map=.=/home/terry/workspace/opensource/webrtc/webrtc-full/src/out/Default" ]
    }

    ...
  }
}

之后,我们可以重新随便编译一个.o来看看。

cd out/Default64
../../third_party/llvm-build/Release+Asserts/bin/clang++ -MMD -MF obj/rtc_base/synchronization/mutex/mutex.o.d -fdebug-prefix-map=../..=/home/terry/workspace/opensource/webrtc/webrtc-full/src -D_GNU_SOURCE -DANDROID -DHAVE_SYS_UIO_H -DANDROID_NDK_VERSION_ROLL=r20_1 -DCR_CLANG_REVISION=\"n359864-04b9a46c-1\" -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_ENABLE_NODISCARD -D_LIBCPP_DEBUG=0 -DCR_LIBCXX_REVISION=375504 -D_DEBUG -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DWEBRTC_ENABLE_PROTOBUF=1 -DWEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE -DRTC_ENABLE_VP9 -DHAVE_SCTP -DWEBRTC_ARCH_ARM64 -DWEBRTC_HAS_NEON -DWEBRTC_LIBRARY_IMPL -DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=1 -DWEBRTC_POSIX -DWEBRTC_LINUX -DWEBRTC_ANDROID -DABSL_ALLOCATOR_NOTHROW=1 -I../.. -Igen -I../../third_party/abseil-cpp -fno-strict-aliasing --param=ssp-buffer-size=4 -fstack-protector -funwind-tables -fPIC -fcolor-diagnostics -fmerge-all-constants -fcrash-diagnostics-dir=../../tools/clang/crashreports -mllvm -instcombine-lower-dbg-declare=0 -fcomplete-member-pointers -ffunction-sections -fno-short-enums --target=aarch64-linux-android21 -mno-outline -mllvm -basic-aa-recphi=0 -Wno-builtin-macro-redefined -D__DATE__= -D__TIME__= -D__TIMESTAMP__= -Xclang -fdebug-compilation-dir -Xclang . -no-canonical-prefixes -Wall -Wextra -Wimplicit-fallthrough -Wunreachable-code -Wthread-safety -Wextra-semi -Wno-missing-field-initializers -Wno-unused-parameter -Wno-c++11-narrowing -Wno-unneeded-internal-declaration -Wno-undefined-var-template -Wno-psabi -Wno-ignored-pragma-optimize -Wno-implicit-int-float-conversion -Wno-final-dtor-non-final-class -Wno-builtin-assume-aligned-alignment -Wno-deprecated-copy -Wno-non-c-typedef-for-linkage -Wmax-tokens -Oz -fno-ident -fdata-sections -ffunction-sections -fno-omit-frame-pointer -g -Xclang -debug-info-kind=constructor -ggnu-pubnames -Wheader-hygiene -Wstring-conversion -Wtautological-overlap-compare -Wexit-time-destructors -Wglobal-constructors -Wc++11-narrowing -Wimplicit-fallthrough -Wthread-safety -Winconsistent-missing-override -Wundef -Wunused-lambda-capture -Wno-shorten-64-to-32 -Wno-undefined-bool-conversion -Wno-tautological-undefined-compare -std=c++14 -fno-trigraphs -Wno-trigraphs -fno-exceptions -fno-rtti -nostdinc++ -isystem../../buildtools/third_party/libc++/trunk/include -isystem../../buildtools/third_party/libc++abi/trunk/include --sysroot=../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot -Wnon-virtual-dtor -Woverloaded-virtual -c ../../rtc_base/synchronization/mutex.cc -o obj/rtc_base/synchronization/mutex/mutex.o

上面命令添加了:-fdebug-prefix-map=../..=/home/terry/workspace/opensource/webrtc/webrtc-full/src

这样把相对路径改为了绝对路径。

在用objdump分析一下:

objdump -W /home/terry/workspace/opensource/webrtc/webrtc-full/src/out/Default64/obj/api/network_emulation_manager_api/network_emulation_manager.o

.debug_line 节的调试内容转储:

  偏移:                       0x0
  长度:                      1722
  DWARF 版本:                4
  导言长度:        1315
  最小指令长度:              1
  每个指令中最大操作码数:       1
  “is_stmt”的初始值:       1
  行基数:                      -5
  行范围:                      14
  操作码基数:                  13

 操作码:
  Opcode 1 has 0 args
  Opcode 2 has 1 arg
  Opcode 3 has 1 arg
  Opcode 4 has 1 arg
  Opcode 5 has 1 arg
  Opcode 6 has 0 args
  Opcode 7 has 0 args
  Opcode 8 has 0 args
  Opcode 9 has 1 arg
  Opcode 10 has 0 args
  Opcode 11 has 0 args
  Opcode 12 has 1 arg

 目录表 (偏移 0x1c):
  1	/home/terry/workspace/opensource/webrtc/webrtc-full/src/rtc_base
  2	/home/terry/workspace/opensource/webrtc/webrtc-full/src/third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include
  3	/home/terry/workspace/opensource/webrtc/webrtc-full/src/buildtools/third_party/libc++/trunk/include
  4	/home/terry/workspace/opensource/webrtc/webrtc-full/src/third_party/llvm-build/Release+Asserts/lib/clang/11.0.0/include
  5	/home/terry/workspace/opensource/webrtc/webrtc-full/src/third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/sys
  6	/home/terry/workspace/opensource/webrtc/webrtc-full/src/third_party/abseil-cpp/absl/meta
  7	/home/terry/workspace/opensource/webrtc/webrtc-full/src/third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/bits
  8	/home/terry/workspace/opensource/webrtc/webrtc-full/src/api/test

 文件名表 (偏移 0x37b):
  条目	目录	时间	大小	名称
  1	1	0	0	checks.h
  2	2	0	0	stdint.h
  3	3	0	0	memory
  4	3	0	0	__nullptr
  5	4	0	0	stddef.h
  6	3	0	0	cstddef
  7	4	0	0	__stddef_max_align_t.h
  8	3	0	0	stddef.h
  9	3	0	0	cstring
  10	2	0	0	string.h
  11	3	0	0	string.h
  12	3	0	0	cstdint
  13	3	0	0	cstdlib
  14	2	0	0	stdlib.h
  15	2	0	0	malloc.h
  16	3	0	0	math.h
  17	2	0	0	stdio.h

 

可以看到目录表中原来的相对路径,确实被修改为了绝对路径。看来这个编译命令是有效果的。
那就修改一下gn的设置:

//build/config/compiler/BUILD.gn
if (!is_nacl || is_clang) {
      cflags += [ "-g" ]   #-g2  --zlf changed
      #--zlf changed
      cflags += [ "-fdebug-prefix-map=../..=/home/terry/workspace/opensource/webrtc/webrtc-full/src" ]
      cflags += [ "-fdebug-prefix-map=.=/home/terry/workspace/opensource/webrtc/webrtc-full/src/out/Default" ]
}

然后gn gen重新生成.ninja文件,并ninja -C out/Default64重新编译整个工程。

最后使用最新的APK,通过Android Studio调试。再次激动的发现可以在IDE中下断点了。

遗留的问题:调试变量的值无法正确显示,亟待研究。

– 这个已经验证过,把-Oz编译选项要取掉,避免调式变量信息被优化掉。

已经解决: 1.参考上面的代码中的的修改,将编译优化选项关闭掉。保留变量调试信息: cflags += [ “-g” ] #-g2

2. 针对android有一个编译选项:android_full_debug = true ; 可以在args.gn或者gn参数中指定一下,可以关闭OZ优化