前 言

概述

本文档主要介绍ARM GCC工具链的使用。主要包括使用环境、工具链的组成介绍、使用方法以及注意事项。本文档主要指导客户更快地掌握ARM GCC工具链的使用。

读者对象

本文档(本指南)主要适用于以下工程师:

  • 技术支持工程师

  • 软件开发工程师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修订记录

修订日期

版本

修订说明

2022-05-10

00B01

第1次临时版本发布。

2024-01-30

01

第1次正式版本发布。

新增3.4小节。

3.5小节涉及更新。

2024-03-31

02

第2次正式版本发布。

新增“3.7 gperftools使用说明”小节。

2024-06-20

03

第3次正式版本发布。

3.1小节涉及修改。

2024-07-03

04

第4次正式版本发布。

2.3小节涉及更新

新增“ARMv9”小节。

新增“增强优化选项使用说明”小节。

刷新“运行报错”小节的问题案例。

新增“性能问题”小节的问题案例。

新增“预编译使用说明”小节。

3.3小节增加步骤

3.4小节和3.5小节涉及更新。

3.7小节增加须知

5.1.2小节涉及更新。

2025-03-07

05

新增“GDB使用问题”小节。

概述

ARM GCC概述

ARM GCC是一种用于X86架构平台上进行ARM架构可执行文件交叉编译的工具链。

ARM GCC介绍与安装

工具链运行环境

建议使用环境:Ubuntu 16.04或更高版本,以及glibc2.22或以上版本。

工具链类型

ARM GCC分为以下6种类型,如表1所示。

表 1 ARM GCC工具链类型

名称

描述

gcc-20230315-aarch64-v01c01-linux-gnu.tar.gz

目标平台为arm64,使用glibc库。

gcc-20230315-aarch64-v01c01-linux-musl.tgz

目标平台为arm64,使用musl库。

gcc-20230315-aarch64-v01c01-liteos-musl.tar.gz

目标平台为arm64,使用liteos定制musl库。

gcc-20230315-arm-v01c01-linux-gnu.tar.gz

目标平台为arm,使用glibc库。

gcc-20230315-arm-v01c01-linux-musl.tar.gz

目标平台为arm,使用musl库。

gcc-20230315-arm-v01c01-liteos-musl.tar.gz

目标平台为arm,使用liteos定制musl库。

gcc-20240612-arm-v01c02-linux-gnueabi.tgz

目标平台为arm,使用glibc库,定制codesize优化版本。

gcc-20240612-arm-v01c02-linux-musleabi.tgz

目标平台为arm,使用musl库,定制codesize优化版本。

说明: 20230315:工具链发布时间。 v01c01:工具链发布版本。

工具链目录结构

工具链压缩包使用tar工具解压。

示例

使用tar工具解压:tar -xzf gcc-20230315-aarch64-v01c01-linux-gnu.tar.gz
解压后目录如下所示
gcc-20230315-aarch64-v01c01-linux-gnu
├── aarch64-v01c01-linux-gnu-gcc
│   ├── aarch64-linux-gnu                       Compiler-dependent tool
│   ├── bin
│   │   ├── aarch64-linux-gnu-addr2line        Translator between instruction and address
│   │   ├── aarch64-linux-gnu-ar               Static packaging tools
│   │   ├── aarch64-linux-gnu-as               Assembler
│   │   ├── aarch64-linux-gnu-c++              C++ compiler
│   │   ├── aarch64-linux-gnu-c++filt          C++ symbol parser
│   │   ├── aarch64-linux-gnu-cpp              C/C++ preprocessor
│   │   ├── aarch64-linux-gnu-elfedit          Tool to update header of ELF file
│   │   ├── aarch64-linux-gnu-g++              Same as aarch64-linux-gnu-c++
│   │   ├── aarch64-linux-gnu-gcc              C compiler
│   │   ├── aarch64-linux-gnu-gcc-10.3.0       Same as aarch64-linux-gnu-gcc
│   │   ├── aarch64-linux-gnu-gcc-ar           Packaging tool
│   │   ├── aarch64-linux-gnu-gcc-nm           Symbol parser
│   │   ├── aarch64-linux-gnu-gcc-ranlib       Ranlib tool
│   │   ├── aarch64-linux-gnu-gcov
│   │   ├── aarch64-linux-gnu-gcov-dump
│   │   ├── aarch64-linux-gnu-gcov-tool
│   │   ├── aarch64-linux-gnu-gprof
│   │   ├── aarch64-linux-gnu-ld
│   │   ├── aarch64-linux-gnu-ld.bfd
│   │   ├── aarch64-linux-gnu-lto-dump
│   │   ├── aarch64-linux-gnu-nm
│   │   ├── aarch64-linux-gnu-objcopy
│   │   ├── aarch64-linux-gnu-objdump
│   │   ├── aarch64-linux-gnu-ranlib
│   │   ├── aarch64-linux-gnu-readelf          Tool to read ELF file
│   │   ├── aarch64-linux-gnu-size             Tool to get ELF size
│   │   ├── aarch64-linux-gnu-strings          String parser
│   │   ├── aarch64-linux-gnu-strip            Compression tool
│   │   ├── aarch64-v01c01-linux-gnu-addr2line -> ./aarch64-linux-gnu-addr2line
│   │   ├── aarch64-v01c01-linux-gnu-ar -> ./aarch64-linux-gnu-ar
│   │   ├── aarch64-v01c01-linux-gnu-as -> ./aarch64-linux-gnu-as
│   │   ├── aarch64-v01c01-linux-gnu-c++ -> ./aarch64-linux-gnu-c++
│   │   ├── aarch64-v01c01-linux-gnu-c++filt -> ./aarch64-linux-gnu-c++filt
│   │   ├── aarch64-v01c01-linux-gnu-cpp -> ./aarch64-linux-gnu-cpp
│   │   ├── aarch64-v01c01-linux-gnu-elfedit -> ./aarch64-linux-gnu-elfedit
│   │   ├── aarch64-v01c01-linux-gnu-g++ -> ./aarch64-linux-gnu-g++
│   │   ├── aarch64-v01c01-linux-gnu-gcc -> ./aarch64-linux-gnu-gcc
│   │   ├── aarch64-v01c01-linux-gnu-gcc-10.3.0 -> ./aarch64-linux-gnu-gcc-10.3.0
│   │   ├── aarch64-v01c01-linux-gnu-gcc-ar -> ./aarch64-linux-gnu-gcc-ar
│   │   ├── aarch64-v01c01-linux-gnu-gcc-nm -> ./aarch64-linux-gnu-gcc-nm
│   │   ├── aarch64-v01c01-linux-gnu-gcc-ranlib -> ./aarch64-linux-gnu-gcc-ranlib
│   │   ├── aarch64-v01c01-linux-gnu-gcov -> ./aarch64-linux-gnu-gcov
│   │   ├── aarch64-v01c01-linux-gnu-gcov-dump -> ./aarch64-linux-gnu-gcov-dump
│   │   ├── aarch64-v01c01-linux-gnu-gcov-tool -> ./aarch64-linux-gnu-gcov-tool
│   │   ├── aarch64-v01c01-linux-gnu-gprof -> ./aarch64-linux-gnu-gprof
│   │   ├── aarch64-v01c01-linux-gnu-ld -> ./aarch64-linux-gnu-ld
│   │   ├── aarch64-v01c01-linux-gnu-ld.bfd -> ./aarch64-linux-gnu-ld.bfd
│   │   ├── aarch64-v01c01-linux-gnu-lto-dump -> ./aarch64-linux-gnu-lto-dump
│   │   ├── aarch64-v01c01-linux-gnu-nm -> ./aarch64-linux-gnu-nm
│   │   ├── aarch64-v01c01-linux-gnu-objcopy -> ./aarch64-linux-gnu-objcopy
│   │   ├── aarch64-v01c01-linux-gnu-objdump -> ./aarch64-linux-gnu-objdump
│   │   ├── aarch64-v01c01-linux-gnu-ranlib -> ./aarch64-linux-gnu-ranlib
│   │   ├── aarch64-v01c01-linux-gnu-readelf -> ./aarch64-linux-gnu-readelf
│   │   ├── aarch64-v01c01-linux-gnu-size -> ./aarch64-linux-gnu-size
│   │   ├── aarch64-v01c01-linux-gnu-strings -> ./aarch64-linux-gnu-strings
│   │   └── aarch64-v01c01-linux-gnu-strip -> ./aarch64-linux-gnu-strip
│   ├── lib
│   ├── lib64
│   ├── libexec
│   ├── target
│   └── tools
│       ├── install_dynamic_basic_library.sh    
│       ├── install_gcc_toolchain.sh
│       └── rename_toolchain.sh
├── install_gcc_toolchain.sh
├── readme.txt
└── runtime_lib.tar.gz

工具链安装

工具链解压完成后将工具链路径添加到环境变量即可使用,安装完成后运行编译器,在命令行输入--version,可以看到详细的版本信息。

例如:aarch64-linux-gnu-gcc --version

工具链同时也提供以下便利性脚本。

  • root用户一键安装脚本:

    sudo ./install_gcc_toolchain.sh [path]

    将工具链安装到指定目录,不指定路径则默认安装在/opt/linux/x86-arm路径下。

  • 工具链重命名脚本:

    ./rename_toolchain.sh [toolchain_path] [prefix_name]

    运行脚本将创建前缀为[prefix_name]的工具软链接。

    例如: ./rename_toolchain.sh /xxx/arm-v01c01-linux-gnu-gcc/bin arm-v100-linux 将在"/xxx/arm-v01c01-linux-gnu-gcc/bin"目录下创建前缀为"arm-v100-linux-"的工具软链接,例如:arm-v100-linux-gcc。

  • 运行时库安装脚本:

    ./install_dynamic_basic_library.sh [toolchain_path] [install_path] [mcpu_abi_mfpu]

    toolchain_path:必选参数,工具链的根目录,并非工具链的可执行文件路径,如:/xxx/arm-v01c01-linux-gnu-gcc

    install_path :必选参数,指定动态库安装路径为:install_path/lib或install_path/lib64,此路径是制作文件系统打包动态库前的存放路径,烧写镜像后动态库会存放到运行时搜索到的路径。

    mcpu_abi_mfpu:mcpu、软硬浮点运算以及mfpu的组合标识,可选参数,默认为空,配置后此标识指定安装不同处理器、浮点运算类型及fpu下的不同动态库。如果mcpu为cortex-a17,mfloat_abi是硬浮点,mfpu是neon-vfpv4,则组合后的标识为:a17_hard_neon-vfpv4

    例如:./install_dynamic_basic_library.sh /xxx/arm-v01c01-linux-gnu-gcc arm-glibc a53_hard_neon-vfpv4 将"/xxx/arm-v01c01-linux-gnu-gcc"路径下的工具链中mcpu为cortex-a53、硬浮点、fpu为neon-vfpv4的动态库安装到"arm-glibc/lib"目录。

ARM GCC功能简述

ARM GCC multilib和芯片编译链接选项

ARM GCC32位下支持以下芯片,以及各芯片的软硬浮点还有部分FPU。

表 1 ARM GCC 32位multilib和芯片编译链接选项

CPU

float-abi

Fpu

multilib

编译和链接选项(推荐)

cortex-a7

Soft

softfp

hard

Neon-vfpv4

Vfpv4-d16

a7_soft

a7_softfp_neon-vfpv4

a7_hard_vfpv4-d16

a7_hard_neon-vfpv4

-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a9

Soft

softfp

hard

Vfp

vfpv3-d16

vfpv3

Neon

Neon-vfpv4

a9_soft

a9_softfp_neon

a9_softfp_vfp

a9_softfp_vfpv3-d16

a9_softfp_vfpv3

a9_hard_vfpv3-d16

a9_hard_neon

a9_hard_neon-vfpv4

-mcpu=cortex-a9 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a17

Soft

Softfp

Hard

Neon-vfpv4

a17_soft

a17_softfp_neon-vfpv4

a17_hard_neon-vfpv4

-mcpu=cortex-a17 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a17. cortex-a7

Soft

Softfp

Hard

Neon-vfpv4

a17_a7_soft

a17_a7_softfp_neon-vfpv4

a17_a7_hard_neon-vfpv4

-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a53

Soft

softfp

hard

Neon-vfpv4

a53_soft

a53_softfp_neon-vfpv4

a53_hard_neon-vfpv4

-mcpu=cortex-a53 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a73

Soft

Softfp

Hard

Neon-vfpv4

a73_soft

a73_softfp_neon-vfpv4

a73_hard_neon-vfpv4

-mcpu=cortex-a73 -mfloat-abi=softfp -mfpu=neon-vfpv4

cortex-a73.cortex-a53

Soft

Softfp

Hard

Neon-vfpv4

a73_a53_soft

a73_a53_softfp_neon-vfpv4

a73_a53_hard_neon-vfpv4

-mcpu=cortex-a73.cortex-a53 -mfloat-abi=softfp -mfpu=neon-vfpv4

arm926ej-s

soft

NA

armv5te_arm9_soft

-mcpu=arm926ej-s -mfloat-abi=soft

Cortex-a55

Soft

Softfp

Hard

NA

a55_soft

a55_softfp_neon-fp-armv8

a55_hard_neon-fp-armv8

-mcpu=cortex-a55 -mfloat-abi=softfp -mfpu=neon-fp-armv8

cortex-r8

soft

NA

r8_soft

-mcpu=cortex-r8 -mfloat-abi=soft

当前工具链使用的默认架构为armv5t,请使用上表中cpu选项进行合适的配置。

ARM GCC FAQ

ARM GCC变更说明 https://gcc.gnu.org/gcc-10/changes.html

ARM GCC使用手册 https://gcc.gnu.org/onlinedocs/gcc-10.3.0/gcc/

ARM GCC帮助信息查询:

  • aarch64-linux-gnu-gcc -Q --help=W 查看告警选项情况。

  • aarch64-linux-gnu-gcc -O3 -Q --help=optimizer 查看优化选项情况。

代码覆盖率测试

工具链支持gcov代码覆盖率测试,gcov随工具链发布,不需要独立安装。编译任意源码test.c为例,通过gcov生成代码覆盖率报告有以下步骤:

  1. 编译原代码,需添加-ftest-coverage -fprofile-arcs选项,生成覆盖率信息文件test.gcno和可执行程序,如:arm-linux-gnueabi-gcc -ftest-coverage -fprofile-arcs test.c -o test

  2. 运行可执行程序test,生成test.gcda文件。

  3. 使用gcov工具再次编译test.c,如:arm-linux-gnueabi-gcov test.c 。至此生成了代码覆盖率报告test.c.gcov文件。

如用户需要生成更直观、全面的代码覆盖率报告,可借助第三方工具lcov,依赖前面步骤生成的相关数据文件,生成test.info。特别注意的是:

  • 当前工具链gcc版本为10.3.0,lcov版本若低于1.15,则可能出现版本不兼容问题,lcov需升级到1.15或以上版本。

  • lcov可能使用系统默认gcov工具,需要使用--gcov-tool选项指定工具链的gcov。如:lcov -d ./ -c -o test.info --gcov-tool arm-linux-gnueabi-gcov

生成test.info文件后,即可通过genhtml生成HTML版的代码覆盖率报告。如:genhtml test.info -o test.html

图 1 生成HTML版的代码覆盖率报告

地址消毒

工具链支持地址消毒ASAN,用于检测溢出、内存泄露、使用非法内存等内存问题。在编译命令中添加选项-fsanitize=address启用。

使用方法:

  1. 静态编译地址消毒库,统一添加选项:-fsanitize=address -static-libasan

    示例:

    编译:aarch64-linux-gnu-gcc  asan_testsuit_010.c -o asan_testsuit_010 -fsanitize=address -static-libasan -lstdc++
    asan_testsuit_010.c 代码如下:
    #include <pthread.h>
    #include <stdio.h>
    void *Thread1() {
      char str[8388609];
      printf("hello!\n");
    }
    int main() {
      pthread_attr_t attr;
      pthread_t t;
      size_t size=0;
      pthread_attr_init(&attr);
      pthread_attr_getstacksize(&attr, &size);
      printf("%d\n", size);
      pthread_create(&t,&attr , Thread1, NULL);
      pthread_join(t, NULL);
      return 0;
    }
    

    测试用执行结果如图1所示。

    图 1 测试用执行结果

    须知: glibc版本链接时需要添加选项-lstdc++,musl 32位版本增加-latomic选项。

  2. 动态链接地址消毒库,添加选项:-fsanitize=address

  3. asan运行选项环境变量:ASAN_OPTIONS,根据需求export到环境变量中,如:export ASAN_OPTIONS=halt_on_error=0

    常用的有:

    1. halt_on_error=0:检测内存错误后继续运行。

    2. detect_leaks=1:使能内存泄露检测。

    3. malloc_context_size=10:内存错误发生时,显示的调用栈层数为10。

    4. log_path=/xxx/asan.log:内存检查问题日志存放文件路径。

    5. detect_stack_use_after_return=1:检查访问指向已被释放的栈空间。

  4. 定制codesize版本使能了Thumb指令模式,基于栈指针展开的场景不生效。如:检测malloc内存泄漏问题。

Backtrace使用

arm-linux-musleabi工具链支持backtrace,可以列出当前函数调用关系。该功能使用C库的接口,需要包含execinfo.h头文件。

  • 接口简介:

    • 获取当前线程的调用堆栈。

      int backtrace(void **buffer, int size)

      该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在指针列表buffer中,参数size用来指定buffer中最多保存void*元素个数,函数返回实际buffer中元素个数。

    • 获取信息转化。

      char **backtrace_symbols(void *const *buffer, int size)

      该函数将从backtrace函数获取的信息转化为一个字符串数组,参数array为backtrace函数获取的指针数组,参数size是该数组中的元素个数(backtrace 的返回值)。

    • 获取信息转化写入到文件。

      void backtrace_symbols_fd(void *const *buffer, int size, int fd)

      该函数与backtrace_symbols函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中, 每个函数对应一行。

  • 开启backtrace功能:

    • 32bit工具链:添加编译选项-funwind-tables。

    • 64bit工具链:无需添加选项,工具链默认开启了-funwind-tables。

    • 如果需要显示函数名,均需要添加编译选项-rdynamic。

  • 关闭backtrace功能:

    • 32bit工具链不添加额外的编译选项,默认不会打开backtrace功能。

    • 64bit工具链需要添加编译选项:-fno-unwind-tables -fno-asynchronous-unwind-tables。

  • 编译选项说明:

    • -funwind-tables:编译时保留eh_frame段信息。

    • -rdynamic:生成动态符号表,用于最后生成字符串形式的调用栈信息。

示例

编译:arm-linux-musleabi-gcc testcase.c -fno-omit-frame-pointer -funwind-tables -rdynamic -o test
testcase.c 代码如下

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void func (void)
{
  void *array[10];
  char **strings;
  int size, i;

  // 获取栈回溯信息
  size = backtrace (array, 10);

  // 将回溯信息转换成字符串打印出来
  strings = backtrace_symbols (array, size);
  if (strings != NULL)
  {

    printf ("Obtained %d stack frames.\n", size);
    for (i = 0; i < size; i++)
      printf ("%s\n", strings[i]);
  }

  // 将回溯信息写入文件
  int fd = creat("backtrace.txt", O_RDWR);
  if (fd < 0) {
    printf("create file failed failed\n");
  } else {
    backtrace_symbols_fd(array, size, fd);
    close(fd);
  }

  free (strings);
}

int main (void)
{
  func ();
  return 0;
}

测试用例执行结果:

查看backtrace.txt文件中的内容:

libtirpc使用说明

glibc 2.32版本,已经移除sun rpc, GNU官网推荐使用TI-RPC,TI-RPC支持IPv6协议。

musl和glibc的arm linux版本工具链已经集成了第三方库libtirpc,使用方式如下:

添加-ltirpc链接选项,头文件在编译器默认搜寻路径中。

gperftools使用说明

gperftools工具运行时需要perl以及binutils等工具才能使用完整的功能,在运行环境中的系统C库以及相关依赖的库文件需要带有符号表以及调试信息。

  • 下载perl源码:https://github.com/Perl/perl5/releases/tag/v5.38.2

  • 下载交叉编译perl工具:https://github.com/arsv/perl-cross/raw/releases/perl-cross-1.5.2.tar.gz

  • 将以上两个包解压之后,复制perl-cross-1.5.2中的所有内容覆盖perl源码。设置好工具链路径之后进入perl源码路径执行:

    ./configure --prefix=$INSTALL --target=$TARGET

    其中$INSTALL为二进制生成路径。$TARGET为需要运行的平台。

    make -j

    make install

    当系统c库为musl时需要在编译时添加-D_GNU_SOURCE。

    • 下载binutils源码:https://ftp.gnu.org/gnu/binutils/binutils-2.38.tar.gz

    • 将包解压之后,进入到binutils源码路径。执行:

      ./configure --prefix=$INSTALL --target=$TARGET

      其中$INSTALL为二进制生成路径。$TARGET为需要运行的平台。

      make -j

      make install

    • 将生成的install下的bin文件拷贝至板端或者挂载路径。

    • 运行环境需要设置

      export PERL5LIB=$PERL5_LIBS_PATH

      export PPROF_PATH=$PPROF_PATH

      其中PERL5_LIBS_PATH为perl5库的路径需要指定到lib下的perl5。PPROF_PATH为gperftools中的pprof的绝对路径。

      注意:gperftools、perl以及binutils生成的bin文件需要在环境变量中能找到。如果有找不到的情况需要使用export PATH=XXXX:$PATH加入环境变量中或者其他方式让其被找到。

    • gperftools堆栈检查器运行:编译时链接libtcmalloc库或者不做链接处理直接在板端添加环境变量LD_PRELOAD="${PATH}/libtcmalloc.so"

      在板端使用HEAPCHECK=normal bin运行a.out即可开启堆栈检查器。其中HEAPCEHCK的值有以下4个:

      normal、strict、minimal、draconian。

    • gperftools堆栈分析器运行:编译时链接libtcmalloc库或者不做链接处理直接在板端添加环境变量LD_PRELOAD="${PATH}/libtcmalloc.so"

      在板端使用HEAPPROFILE=mybin.prof bin将会生成反馈文件mybin.prof。

      之后可将被测程序bin和反馈文件mybin.prof放至工作环境(x86_64)下或者板端执行:pprof --text bin mybin.prof

    • gperftoolsCPU分析器运行:需要链接libprofiler或者不做链接处理直接在板端添加环境变量LD_PRELOAD="${PATH}/libprofiler.so"

      在代码中需要分析的片段使用ProfilerStart(const char* fname)和ProfilerStop()函数括起来

      在板端使用CPUPROFILE=text.prof bin将会生成反馈文件test.prof

      之后可将被测程序bin和反馈文件test.prof放至工作环境(x86_64)下或者板端执行:pprof –text bin text.prof

    • 详细介绍参考官网https://gperftools.github.io/gperftools

    须知: 在arm32平台使用gperftools工具时被测程序需要使用编译选项-funwind-tables -rdynamic 进行编译。

ARMv9

aarch64平台支持armv9架构,支持SVE/SVE2硬件加速、编译生成ARMv9芯片的程序。

使用方法:选择对应的aarch64平台ARMV9工具链编译即可。

ARM GCC使用与注意事项

编译选项使用方式

如果要启用函数内联优化,则添加-finline-functions使能该优化:

aarch64-linux-gnu-gcc -finline-functions testcase.c

如果要禁用一个优化,则可以在编译时删除对应的优化选项,或者在命令行末尾添加-fno-XXX以禁用该优化。

在如上所述启用函数内联优化后,要禁用该优化:

aarch64-linux-gnu-gcc -finline-functions testcase.c -fno-inline-functions

使用#pragma控制函数优化

ARM GCC在-O0以上的优化级别通常会消除冗余代码,以获得更好的性能。在此版本的编译器中,提供了 #pragma GCC optimize ("-O0") 这个标签,供用户禁用特定函数的更高级别的优化。如果开发人员在函数的开头添加此标签,当编译器编译此程序时,优化级别超过-O0(-O1、-O2、-Os等),此函数仍将使用-O0级别优化编译。

示例

void Func1(int *a)
{
    *a = 1;
}
#pragma GCC optimize ("O0")
void Func2(int *a)
{
    *a = 1;
}

int main()
{
    int a = 0;
    Func1(&a);
    Func2(&a);
}

在此示例中,函数“Func2()”仍将以-O0级别的编译,尽管编译器指示了其他的优化级别选项。如果指示-O3编译此程序,则最终汇编文件如下所示:

0000000000000000 <Func2>:
   0:   d10043ff        sub     sp, sp, #0x10
   4:   f90007e0        str     x0, [sp, #8]
   8:   f94007e0        ldr     x0, [sp, #8]
   c:   52800021        mov     w1, #0x1                        // #1
  10:   b9000001        str     w1, [x0]
  14:   d503201f        nop
  18:   910043ff        add     sp, sp, #0x10
  1c:   d65f03c0        ret

0000000000000020 <main>:
  20:   a9be7bfd        stp     x29, x30, [sp, #-32]!
  24:   910003fd        mov     x29, sp
  28:   b9001fff        str     wzr, [sp, #28]
  2c:   910073e0        add     x0, sp, #0x1c
  30:   94000000        bl      50 <Func1>
  34:   910073e0        add     x0, sp, #0x1c
  38:   94000000        bl      0 <Func2>
  3c:   52800000        mov     w0, #0x0                        // #0
  40:   a8c27bfd        ldp     x29, x30, [sp], #32
  44:   d65f03c0        ret
  48:   d503201f        nop
  4c:   d503201f        nop

0000000000000050 <Func1>:
  50:   52800021        mov     w1, #0x1                        // #1
  54:   b9000001        str     w1, [x0]
  58:   d65f03c0        ret

可以观察到,Func1通过消除一些冗余代码进行了优化。但Func2保留了所有这些冗余代码,并保持了-O0优化级别。

工具链常用硬件编译选项

对于32位arm需要指定-mcpu、-mfloat-abi和-mfpu。

对于64位aarch64需要使用-mcpu 。

当-mfloat-abi=hard时需要确定的fpu,并且有耦合的模块需要使用相同的选项进行编译链接。

-nostartfiles选项说明

-nostartfiles选项是编译器命令行选项,而不是链接器命令行选项。-nostartfiles意思是链接时不要包含标准启动文件(例如 crt1.o、crtbegin.o、crtend.o 等)。

例如:aarch64-linux-gnu-ld testcase.o -nostartfiles ...

早期版本链接器未做参数检查所以不会报错,此版本上做了严格的参数检查,链接器直接使用-nostartfiles选项会因无法识别该选项而报错。

增强优化选项使用说明

除GCC-10.3版本社区通用功能和优化外,aarch64工具链增加了新的功能支持,如加权预取距离优化、四精度浮点等;中后端性能优化相对社区版本也进行了增强,包括循环向量化、SLP向量化、浮点优化、内存优化、指令优化等。用户可根据需求开启选项,使高性能计算领域能获得更优的性能收益。

选项 -fprefetch-loop-arrays

该选项对预取距离的计算算法进行改进,增加了分支加权的预取距离算法,根据循环中的分支概率进行加权的计算执行时间并计算更准确的预取距离。可结合PGO/AutoFDO的反馈优化对CFG的分支概率进行修正,来提高预取计算的准确性,同时也支持传入cache misses的profile进行解析并用于场景和访存对象的筛选。

【使用方法】

  • -fprefetch-loop-arrays=0:原始预取算法(无赋值,默认为0);

  • -fprefetch-loop-arrays=1:简化分支的预取距离算法;

  • -fprefetch-loop-arrays=2:分支加权的预取距离算法;

  • -fprefetch-loop-arrays=[value] -fauto-profile=xxx.gcov -fcache-misses-profile=xxx.gcov:反馈式软件预取;

可配置参数:

  • --param param-prefetch-func-topn=n:筛选前n个热点函数,默认值:3

  • --param param-prefetch-ref-topn=n:筛选前n个热点访存对象,默认值:5

  • --param param-high-loop-execution-rate=n:筛选执行率高于n%的循环,默认值:95%

选项 -fipa-struct-reorg

该选项针对结构体内的成员冷热差异的场景,结构体成员在内存中的排布进行新的排列组合,来提高cache的命中率。

【使用方法】

在选项中加入-O3 -flto -flto-partition=one -fipa-struct-reorg即可。注意选项需要在-O3 -flto -flto-partition=one全局同时开启的基础上才使能。

选项 -ftree-vect-analyze-slp-group

该选项在SLP矢量化阶段,仅进行reduction chains group的分析以及矢量化。在某些无法自动分析最优方案的情况打开该选项进行矢量化控制。

【使用方法】

在打开矢量化控制开关-ftree-vectorize的基础上,在选项中加入-ftree-vect-analyze-slp-group

选项 -fp-model

该选项在gcc的基础上开发,控制浮点数计算精度,损耗交换结合律、强度折减等部分精度达到提高性能。

  • -fp-model=normal:默认值,等同于不开任何fp-model选项,对其他选项无任何影响。

  • -fp-model=fast:等同于打开-ffast-math,会开启各种损精度的优化,如交换结合律、强度折减等。

  • -fp-model=precise:关闭所有损害精度的优化,保证浮点结果的正确性。包括关闭交换结合律、强度折减、向零舍入、fma指令生成等优化。

  • -fp-model=except:开启浮点计算异常机制,编译器在此做的主要工作是考虑浮点异常的存在,阻碍不考虑浮点异常的优化。

  • -fp-model=strict:等同于打开以上-fp-model=precise -fp-model=except

【使用方法】

根据需求加入对应选项即可。

选项 -fftz

该选项是用来控制浮点数行为从而提升性能的选项,选项描述如下:-fftz/-fno-ftz打开/关闭向零舍入(flush to zero)特性。

【使用方法】

根据浮点行为的需求加入选项-fftz即可。

选项 -fipa-reorder-fields

该选项可根据结构体中成员的占用空间大小,将成员从大到小排列,以减少边界对齐引入的padding,来减少结构体整体占用的内存大小,以提高cache的命中率。

【使用方法】

在选项中加入-O3 -flto -flto-partition=one -fipa-reorder-fields即可。

选项 -fipa-struct-reorg=n

使用该选项控制内存空间布局优化系列优化。

  • -fipa-struct-reorg=0 不启用任何优化。

  • -fipa-struct-reorg=1 启用结构体拆分和结构体数组优化,等同使用-fipa-struct-reorg。

  • -fipa-struct-reorg=2 在 -fipa-struct-reorg=1 的基础上,新增结构体成员重排-fipa-reorder-fields。

  • -fipa-struct-reorg=3 在-fipa-struct-reorg=2的基础上,新增结构体冗余成员消除优化。可消除结构体冗余成员,删除冗余的写语句。

  • -fipa-struct-reorg=4 在-fipa-struct-reorg=3的基础上,新增安全结构体指针压缩优化。可缩小结构体占用内存大小,降低从内存中读写数据时的带宽压力,从而提升性能。仅支持结构体数组大小在编译期间已知的场景。

  • -fipa-struct-reorg=5 在-fipa-struct-reorg=4的基础上,放宽了结构体指针压缩优化的应用场景。

  • -fipa-struct-reorg=6 在-fipa-struct-reorg=5的基础上,新增结构体数组semi-relayout优化。semi-relayout在一定范围内,将结构体数组中各结构体的成员打包后重排,提高数据空间局部性,从而提升性能。

【使用方法】

在选项中加入-O3 -flto -flto-partition=one -fipa-struct-reorg=n即可,n取值范围为[0,6]。注意-fipa-struct-reorg=n选项,需要在-O3 -flto -flto-partition=one全局同时开启的基础上才使能。

选项 -ftree-slp-transpose-vectorize

该选项在循环拆分阶段,增强对存在连续访存读的循环的数据流分析能力,通过插入临时数组拆分循环;在SLP矢量化阶段,新增对grouped_stores进行转置的SLP分析。

【使用方法】

在选项中加入-O3 -ftree-slp-transpose-vectorize即可。注意-ftree-slp-transpose-vectorize选项,需要在-O3开启的基础上才使能。

选项 -farray-widen-compare

该选项在数组比较场景下,提供数组元素并行比较,以提高执行效率。

【使用方法】

在选项中加入-O3 -farray-widen-compare即可。注意-farray-widen-compare选项,需要在-O3开启的基础上才使能。

选项 -fccmp2

该选项在gcc社区版本基础上,增强ccmp指令适用场景,简化指令流水。

【使用方法】

选项控制开关-fccmp2,默认关闭。

预编译使用说明

当前版本工具链开启了安全编译选项PIE,PCH功能无法使用。

ARM GCC 常见问题

编译报错

编译警告增强导致的编译错误

问题描述:由于编译警告被增强,导致出现了编译错误,错误信息如下。

error: implicit conversion from 'ext_jpeg_fmt' to 'eapi_jpgd_fmt' [-Werror=enum-conversion]
29 |     image_info->fmt = uapi_image_info->fmt;

原因分析:此类编译错误是因为在编译选项中添加了-Werror导致的。

修改建议:对于这些警告,应先理解其含义,并通过修改代码来消除警告。非代码问题的警告可以使用-Wno-XXXX来屏蔽,或使用-Wno-error=XXXX将error降级为warning。警告选项含义可通过aarch64-linux-gnu-gcc --help=warning查看,而默认启用的警告选项则可以通过arm-v100-linux-gcc -Q --help=warning查看。

未使用-mcpu或-march选项指定指令集架构导致高版本指令不识别

问题描述:由于没有通过-mcpu或-march选项指定具体的指令集架构,编译器未能识别较高版本的指令。

/tmp/ccprUPs8.s:402: selected processor not support ‘qadd r4,r4,lr’ in ARM mode

原因分析:arm-linux-gnueabi工具链架构默认为armv5t,示例中qadd指令属于v5T-E、v6 等更高版本指令集架构。

修改建议:根据实际硬件指定-mcpu、-march、-mfloat-abi、-mfpu等硬件编译选项,充分利用硬件能力。

  • 可以通过以下命令来了解这些编译选项的具体含义:

    arm-linux-gnueabi-gcc --help=target 
    
  • 要查看当前工具链默认启用的硬件编译选项,可以执行:

    arm-linux-gnueabi-gcc -Q --help=target 
    
  • 若想查看特定CPU的默认硬件编译选项状态,可使用:

    arm-linux-gnueabi-gcc -Q -mcpu=XXX --help=target 
    
  • 对于特定架构的默认硬件编译选项状态,可查询:

    arm-linux-gnueabi-gcc -Q -march=XXX --help=target 
    

函数式宏名称与函数名相同,入参不同导致编译报错

问题描述:当函数式宏的名称与某个函数的名称相同时,即使它们的入参不同,也会导致编译错误。

error: macro "atomic_load" passed 2 arguments, but takes just 1

原因分析:由于函数式宏名称和函数名相同,入参个数不同,函数式宏定义在函数前,工具链预处理时将函数名当做宏展开。

修改建议:为了避免这种混淆,建议不要使用与现有函数名称相同的函数式宏名称,以确保工具链能够正确地区分宏和函数。

C++函数同时在声明和定义时指定了默认参数导致编译错误

问题描述:在C++中,如果函数的声明和定义都指定了相同的默认参数,会导致编译错误,错误信息如下。

test.cpp:7:6: error: default argument given for parameter 1 of ‘void Test::fun(int, bool)’
    7 | void Test::fun(int a=1000, const bool b=false)
In file included from test.cpp:1:
test.h:10:7: note: previous specification in ‘virtual void Test::fun(int, bool)’ here
   10 |  void fun(int a=1000, const bool b=false);

原因分析:C++函数默认参数只允许在声明时指定,不需要在定义时指定。

修改建议:将定义处的默认参数删除。C++代码不建议使用-fpermissive选项将语法错误降级为警告。

C++标准库基本类型不识别

问题描述:在当前环境中,C++ 标准库中的基本类型未被正确识别。

test.cpp:6:10: error: ‘string’ is not a member of ‘std’
    6 |     std::string str = "hello world";

原因分析:此版本工具链去除冗余包含,优化了头文件包含链,将使用C++标准库基本类型却未显式包含对应的头文件的问题暴露出来。

修改建议:代码应显式包含C++标准库基本类型对应的的头文件,增强代码兼容性。运行 arm-linux-gnueabi-gcc -M test.c 可查看头文件包含链。

GLIBC 2.34 部分宏不再定义为常量

问题描述:在 GLIBC 2.34 中,部分宏不再被定义为常量。

./exsample.c:45:57: error: missing binary operator before token “(”
45 | #if defined(PTHREAD_STACK_MIN) && THREAD_MINSTACKSIZE < PTHREAD_STACK_MIN

原因分析:GLIBC 2.34 中 PTHREAD_STACK_MIN、MINSIGSTKSZ和SIGSTKSZ 不再定义为常量,重新定义为sysconf(XXXX)函数调用,工具链预处理时宏无法展开为常量导致编译错误。

修改建议:请勿在预处理器指令中将这三个宏当做常量进行比较,请在代码中实现相应比较逻辑。

GLIBC 2.34 删除了某些头文件导致头文件找不到

问题描述:由于 GLIBC 2.34 版本删除了一些头文件,导致编译时出现头文件缺失的问题。具体编译报错信息如下。

../exsample.c:41:10: fatal error: sys/sysctl.h: No such file or directory 
41 | #include <sys/sysctl.h>

原因分析:由于GLIBC 2.34 版本删除了<sys/sysctl.h>头文件和sysctl函数,导致编译时出现头文件缺失的问题。

修改建议:为了确保代码具有最小的依赖性,建议删除冗余的头文件引用。

工具链默认架构为armv5t,导致部分测试用例执行失败

问题描述:由于工具链修改了默认cpu选项导致dejagnu中依赖neon的相关用例执行失败。具体编译报错信息如下。

Excess errors:
cc1: warning: switch '-mcpu=cortex-a15' conflicts with switch '-march=armv7-a+simd'
FAIL: gcc.target/arm/memcpy-aligned-1.c scan-assembler-times str\t 1
FAIL: gcc.target/arm/memcpy-aligned-1.c scan-assembler-not ldr\t

原因分析:需要开启CPU选项之后才能支持neon的使用,默认的v5t版本太老导致有些特性不支持所以出现生成的指令与预期不符合。

修改建议:添加选项-mcpu=cortex-a9

5.19 GLIBC 2.31 不支持静态编译librpc库

问题描述:由于 GLIBC 2.31 版本不再向后兼容,导致librpc库无法进行静态编译,导致下列报错

inetd.c:(.text.reread_config_file+0x270): undefined reference to `getrpcbyname'

原因分析:由于GLIBC 2.32 版本进行了结构性的变化,导致之前版本与glibc >= 2.32的版本无法进行兼容,无法使用--enable-static-nss来使得nss模块支持静态链接,但是目前来看Emei版本就算能启用该编译选项依旧无法实现,因为在Emei版本中nss模块启用了compat,db,dns,files,hesiod,如果启用该编译选项会导致compat,db,hesiod这些依赖于外部库的模块在静态编译时无法被正确链接。

修改建议:仅使用默认的动态编译即可。

链接报错

硬件编译选项与第三方库的不一致或编译时的硬件编译选项与链接时的不一致

问题描述:硬件编译选项与第三方库之间存在不匹配,或者在编译阶段和链接阶段使用的硬件编译选项不一致。错误信息如下:

error: ./libtest.a(test.o) uses VFP register arguments, a.out does not failed to merge target specific data of file ./libtest.a(test.o)

原因分析:硬浮点(Hard Float)和软件浮点(Soft Float)ABI不兼容。

修改建议:库架构相关信息可通过命令arm-linux-gnueabi-readelf -A 查看。如果编译和链接是分开进行的,则必须在CFLAGS和LDFLAGS加上相同的硬件编译选项,否则链接时会使用工具链默认配置导致链接错误。

编译ko找不到符号

问题描述:在编译名为exsample.ko的模块时遇到了错误,具体表现如下。

ERROR:modpost: “_mcount” [exsample.ko] undefined!
ERROR:modpost: “__stack_chk_guard” [exsample.ko] undefined!

原因分析:从GCC 9.1版本开始,支持了-mstack-protector-guard=global或sysreg选项,编译内核生成vmlinux时,内核会判断编译器是否支持该选项从而使用-mstack-protector-guard=sysreg,故生成的vmlinux中不会携带__stack_chk_guard,而是使用寄存器来做作为guard,所以会报找不到这个定义的错误,mcount的原因也是类似。

修改建议:为了解决上述问题,可采取以下措施之一。

  1. 修改内核编译脚本,禁用-mstack-protector-guard=sysreg选项,以确保生成的vmlinux包含__stack_chk_guard。

  2. 使用高版本GCC,并且采用相同的编译选项来重新编译驱动程序,这样可以避免对__stack_chk_guard和_mcount的依赖。

通过以上方法,可以有效地解决编译过程遇到的未定义符号错误。

共享库名不满足libnamespec.so格式,无法通过-lnamespec链接

问题描述:当共享库名称不符合 libnamespec.so格式时,链接器将无法通过 -lnamespec选项正确地找到并链接该库。

原因分析:在支持共享库的系统上,使用-lnamespec指定链接库时,链接器会优先搜索名为libnamespec.so的共享库,若未找到该共享库,则会继续搜索名为libnamespec.a的静态库。因此,当共享库名带有版本号(如 libnamespec.so.xxx)时,链接将无法通过-lnamespec正确链接。

修改建议:对于带有版本号的共享库libnamespec.so.xxx创建名为libnamespec.so的软链接或者使用-l:libnamespec.so.xxx指定具体库名进行链接。

例如,对于名为libc.so.1的共享库可使用 -l:libc.so.1 来进行链接。

运行报错

设备U-Boot启动异常

问题描述:设备的U-Boot启动过程中出现异常。

原因分析:由于编译工具链默认使能-munaligned-access选项,该选项允许内存非对齐访问。但ARM Device或Strong-ordered内存区域禁止内存非对齐访问。

修改建议:为了确保兼容性和稳定性,建议在编译时添加-mno-unaligned-access选项以禁用内存非对齐访问。

符号找不到

glibc版本符号找不到

问题描述:运行程序时遇到以下错误信息,表明当前运行环境中的glibc版本过低,无法满足程序运行所需的最低glibc版本要求。

./a.out: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./a.out)

原因分析:编译链接时使用了高版本glibc,而运行环境使用的低版本的glibc,这导致可执行文件或共享库中包含了一些仅在高版本的glibc中才有的符号,运行时在低版本的glibc上找不到这些符号,从而引发错误。

修改建议:glibc符号具有向后兼容性因此请确保在编译链接时使用的glibc版本与运行环境中的glibc版本相匹配。请勿将使用高版本glibc编译链接的可执行文件或共享库在低版本glibc环境中运行。

缺少符号pmap_unset 和 pmap_set

问题描述:在使用glibc 2.34版本的工具链时,遇到缺少符号`pmap_unset`和`pmap_set`的问题。这导致BusyBox中的RPC功能无法正常使用。

原因分析:BusyBox的RPC功能开启后需要依赖RPC库。而glibc2.34版本中,低版本自带的rpc库已经被移除,并且官方推荐使用开源第三方tirpc库。由于BusyBox默认并未集成tirpc库,因此在使用glibc2.34版本时,会出现缺少pmap_unset 和 pmap_set符号的错误。

修改建议:arm工具链已集成了tirpc库,用户可通过在busybox的.config中CONFIG_EXTRA_LDLIBS处赋值-ltirpc即可解决该问题。

缺少符号stime

问题描述:在使用glibc 2.34版本的工具链编译程序时,遇到了与`stime`符号相关的错误。

原因分析:由于使用glibc2.34版本的工具链,而stime符号在glibc2.31及以上的版本已废弃,因此,如果低版本的BusyBox中的date模块依赖于函数,则会出现相关错误。

修改建议:

  1. 升级到busybox1.3或者更高版本,因为高版本的BusyBox已经修复了这个问题 ;

  2. 在BusyBox配置中禁用date功能。

默认版本的基础库无法显示行号和文件名

问题描述:默认版本的基础库无法显示行号和文件名。

原因分析:默认版本的基础库中未包含debug信息,因此无法显示行号和文件名。

修改建议:以调试补丁的形式提供携带debug信息的基础库。

使用musl工具链编译第三方工具dropbear运行时失败,对比使用glibc工具链无异常

问题描述:使用musl工具链编译第三方工具dropbear时,运行时出现失败,而使用glibc工具链无异常。

原因分析:dropbear开源软件使用musl版本工具链动态链接时,默认配置选项"-Wl,-pie"。PIE需要在编译和链接阶段都正确配置。只使用-Wl,-pie,没有在编译阶段设置-fPIE,导致生成的代码不是位置无关的,从而在链接时出现警告,会导致运行时出现了Segmentation fault错误。

修改建议:在动态链接时,应额外添加编译选项"-pie",以替代dropbear自身的"-Wl,-pie"选项。

串口回显功能异常

问题描述:串口回显功能异常

原因分析:当前通过getchar刷新stdout缓存的方式不符合POSOX标准,因此在使用musl库时,该方法无法确保数据能够实时输出。

修改建议:

  • 每次调用printf之后,应调用fflush(stdout)以强制刷新stdout缓存。

  • 进程启动后,在printf前调用setbuf(stdout, NULL)关闭整个进程的stdout的默认行缓存策略。

性能问题

musl基础库性能比glibc差

问题描述:在使用musl基础库时,相较于glibc,其性能表现可能略逊一筹。

原因分析:这主要是由于原生musl基础库在某些特定场景下的实现方式与glibc有所不同,导致执行一些关键操作时时效率较低。

修改建议:

  • 优化数学函数和字符串处理:可以考虑引入optimized-routines库,该库专门针对常用数学运算函数及字符串处理进行了优化,能够显著提高相关操作的速度,并且已经被集成到了当前使用的工具链中;

  • 改善内存管理:对于内存分配和释放等操作,推荐采用mimalloc库来替代默认的内存管理机制。mimalloc库在多线程环境下尤其表现出色,能有效减少内存碎片并加快内存分配速度,注意需要使用mimalloc前需要先自行完成编译和集成工作。

GDB使用问题

加载共享库失败

问题描述:未能成功加载共享库。在尝试加载共享库时遇到以下错误。

error while loading shared libraries: libncursesw.so.6: cannot open shared object file: No such file or directory!
error while loading shared libraries: libtinfo.so.6: cannot open shared object file: No such file or directory!

原因分析:

出现上述错误的原因是缺少相应的共享库libncursesw.so.6和 libtinfo.so.6。

修改建议:

  • 在Ubuntu 20.04.5 LTS及以上版本系统运行gdb。

  • 通过以下命令安装缺失的库:

    apt-get install libncursesw6
    
    apt-get install libtinfo6