代码分析

1 Call graph

分析函数调用关系图 (call graph) 的几种方法

1.1 静态

1.1.1 Doxygen + Graphviz + Htmlhelp (windows)

  • 使用 Doxygen + Graphviz + Htmlhelp,Doxygen 配置,再加上下面两图,不要选生成 Chinese,否则 chm 文件乱码

  • 生成前将不用的第三方库代码移走

  • Doxygen error: failed to run html help compiler on index.hhp 报错似乎不影响

1.1.2 SciTools Understand (windows)

C/C++ 项目参考Buildspy - For gcc/g++ Users

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用 cygwin 的 /x86_64-w64-mingw32-gcc.exe 编译 openssl

# buildspy.exe、g++wrapper.exe、gccwrapper.exe 移入上一层目录(\SciTools\bin\pc-win64),否则找不到 qt 等 dll
# 正常配置好项目
./Configure --prefix=/cygdrive/d/Desktop/openssl2/openssl-openssl-3.0.0-alpha9/install mingw64
make clean
# 为了找到 buildspy 和 wrapper
export PATH="/cygdrive/d/MySoftware/SciTools/bin/pc-win64:$PATH"
# 替换编译器
export CC=gccwrapper
buildspy.exe -db openssl.udb -verbose -cmd make
# 打开 openssl.udb
project -> analyze all files
project -> improve project accuracy -> missing includes 添加 include 文件 C:\cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\include
  • 对于 openssl,一处非常奇怪的宏定义字符串导致编译不过问题,原 Makefile 是 -DOPENSSLDIR=”"$(OPENSSLDIR)",使用 gcc 编译没问题,但使用 gccwrapper 就要改成 -DOPENSSLDIR=’ “$(OPENSSLDIR)”‘,注意单引号与双引号间的空格不能省略。在同一行有 3 处都要改。宏定义问题可以通过 gcc 加 -v 参数来调试

1.1.3 SciTools Understand (linux)

1
2
3
4
5
6
7
8
9
10
11
12
# 以 openssl 为例
make clean
export PATH="/home/zack/code/proj/scitools/bin/linux64/buildspy:$PATH"
# 配置 CC 要在 ./config 之前
export CC=gccwrapper
./config
# 正确的话应该看到 buildspy 的打印
buildspy -db openssl.udb -verbose -cmd make
# 打开 openssl.udb
# project -> analyze all files,点击结果的 warning 窗口中的 search for missing includes
# 把 gcc 的查找路径添加进去 echo | gcc -E -Wp,-v -
# 另外手动定义两个宏 __x86_64__ 和 __LP64__ 为了 include 64位的头文件 <gnu/stubs-64.h>

1.1.4 Sourcetrail

用 compilation databases 新建项目

  • linux 下用 bear 生成
  • 全平台使用 cmake 生成,set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

参考这两个
https://aul12.me/embedded/2019/05/27/cubemx-cmake.html
https://clang.llvm.org/docs/JSONCompilationDatabase.html

  • stm32 工程使用 cmake,从而生成 compilation databases
  • 使用 stm32 官方 ide

1.2 动态

1.2.1 KcacheGrind (linux)

tools-to-get-a-pictorial-function-call-graph-of-code

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo apt install kcachegrind valgrind

# Compile the program as usual, no special flags.
# 这里的调试信息和优化可以参考 工作->SDK.md 中对 openssl 的调试选项 export CC="gcc -g3 -ggdb -gdwarf-4 -fno-inline -O0 -fno-omit-frame-pointer"
gcc -ggdb3 -O0 -o main -std=c99 main.c

# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main arg1 arg2 ...
# 加参数这种更全,没详细研究
valgrind --tool=callgrind --dump-instr=yes --collect-jumps=yes ./main arg1 arg2 ...

# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
  • 可以查看最大开销的调用栈 settings -> sidebars -> call stack ,实际的调用栈猜测是有多条的,还是 gdb 断点看吧
  • call graph 右键设置好 caller 和 callee depth 后(设小点,防止之后产生的图太大),右键 node cost 可以设置为 no minimum,就可以看到所有的 callee 和 caller 了
  • 要看某函数调的所有函数,除了上面改 cost 的方法,还可以看下面第一栏的 Callees
  • 生成整张调用图(很大)的另一个工具
  • 右键可以导出成 DOT、png、jpg 文件

1.2.2 gcc instrumental (linux)

  • 看 etrace 部分的回答instrumental,这两个程序使用查找 elf 程序中符号“地址”与“名称“ map 的方式生成调用关系,通过调试 instrument 附带的 C 代码发现,linux 上 __cyg_profile_func_enter 传入的函数指针地址与符号表中的不同,可能的原因 (这篇文章是通过符号名查地址)

  • 所以基于 instrumental 的代码改进,使用 dladdr 接口获得地址对应的符号名,除了 -finstrument-functions,还要加 -O0 -rdynamic -ldl,不用加 -g

  • 类似的还有 backtrace_symbols

  • 代码见 instrumental 文件夹,目前还不支持多线程,没有锁和分线程统计

    1
    2
    3
    4
    5
    6
    7
    gcc -shared -fPIC -Wall inst.c -o inst.so -ldl
    gcc -rdynamic -finstrument-functions -O0 -o code code.c
    LD_PRELOAD=/home/inst.so ./code

    # 以 openssl 为例,openssl 因为有 static 函数,所以效果不太好
    export CC="gcc -rdynamic -finstrument-functions -O0 "
    ./config
  • TODO 想支持多线程,把 openssl 的图画出来,把 static 函数批量替换会怎样?可参考 github 上 ftracer

1.3 手绘

  • xmind 的逻辑图,比起 mindmaster 没有节点数目的限制,可以一个文件创建多个画布,免费导出 svg

2 内存占用

valgrind-3.16.1 交叉编译,参考

  • Supported Platforms,对 kernel 和 glibc 的版本有要求
  • –prefix 的路径要和最终运行的路径一致
  • 修改 configure armv7*) 改为 armv7*|arm*)
  • ./configure –prefix=/data/update/valgrind –host=arm-linux-gnueabi , make, make install
  • 精简 lib/valgrind 目录下文件 rm callgrind-arm-linux helgrind-arm-linux cachegrind-arm-linux dhat-arm-linux drd-arm-linux lackey-arm-linux exp-bbv-arm-linux,减少占用
  • 要统计的程序使用 -g 编译,-O 优化等级不重要,不会影响堆内存统计
  • 获得最大栈深,根据手册所说栈采样会慢 /data/update/valgrind/bin/valgrind –tool=massif –heap=no –stacks=yes ./sdk 。但这样无法得到最大栈深的调用栈,将线程的栈故意比最大栈深小一些,使用 gdb 调试,得到触发 stackoverflow 的地方。实测,用 ulimit -s 调用栈打不出来

valgrind Massif官方文档

栈统计的几种方法

Properly allocating stacks,这篇文章一共 3 部分Computing your stack size ,google ”find max stack depth by stack overflow“ 可以看到 keil 和 IAR 的文章,linux 平台上能想到的办法是改 massif 的源码来记录 detailed snapshot

3 调试

3.1 gdb

https://www.linux.com/news/remote-cross-target-debugging-gdb-and-gdbserver/

https://sourceware.org/gdb/wiki/BuildingCrossGDBandGDBserver,包含 host、target 解释

1
2
3
4
5
6
7
# 记得装这个
sudo apt-get install texinfo
# 可以创建不同的 build 文件夹来编译
# gdb & gdbserver
../configure --host=mipsel-openwrt-linux --prefix=/home/zack/code/proj/gdb_install
# gdb
../configure --with-expat --target=mipsel-openwrt-linux --prefix=/home/zack/code/proj/gdbonly_install

3.1.1 gdbserver

  • 传入环境变量方法
    • sudo gdbserver –wrapper env ‘LD_LIBRARY_PATH=/lib/libpcap/lib’ – :1234 ./tiny

3.1.2 修改

为了编译通过做得修改,ubuntu 18.04 环境。基于 master 分支 ee6d95574b 2019-09-04 (HEAD -> master, origin/master, origin/HEAD) Automatic date update in version.in

diff 文件

1
2
# 拷贝到源码目录,使用 patch 
patch -p1 <patch.diff

3.1.3 调试

100 个 gdb技巧

1
2
3
4
5
6
7
8
9
gdbserver <host-ip>:2345 tinyeye 

./mipsel-openwrt-linux-gdb tinyeye
target remote 192.168.37.1:2345
continue

# 每行打印一个结构体成员
set print pretty on
p *(struct list_head *)0x469594

segfaults 常见定位方法