Linux C应用开发系列
第一篇 利用VSCode+cmake+GDB+gdbserver 实现I.MX6ULL ARM开发板的gdb在线调试
一、实现过程
1.1 在Ubuntu下通过VSCode创建工程和工作区;
1.2 编写各级目录下的 CMakeLists.txt 文件;
1.3 编写 arm-linux-setup.cmake 配置文件,并在其中添加支持GDB调试的配置语句;
1.4 使用cmake工具构建生成Makefile;
1.5 使用make命令编译工程生成带调试信息的可执行文件,并将可执行文件拷贝到开发板中;
1.6 修改VSCode工程下面的launch.json文件,该文件需要根据实际情况配置;
二、详细步骤
2.1 在VSCode里创建工程
目录结构:
最终的目录结构就如下所示:
├── build
│ ├── bin
│ │ └── gdb_gpioAPP
│ └── lib
│ └── libgdb_gpioAPP.a
├── CMakeLists.txt
├── libbsp
│ ├── CMakeLists.txt
│ ├── gpio_output.c
│ └── gpio_output.h
└── src
│ ├── CMakeLists.txt
│ └── main.c
└── arm-linux-setup.cmake
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include “gpio_output.h”
int main(int argc, char *argv[])
{
int ret;
char file_path[100] = {
0};
if((argc != 2) && ((argv[0] != Y_GPIO_OUTPUT_APP) || (argv[0] != Y_GDB_GPIO_OUTPUT_APP)))
{
perror(“GPIO APP format error!!!\r\n”);
exit(-1);
}
strcpy(file_path, Y_GPIO_PATH);
strcat(file_path, “/gpio”);
strcat(file_path, argv[1]);
ret = access(file_path, F_OK); //F_OK -> Test for existence. 0表示成功,成功即意味文件存在;-1表示失败,失败即意味文件不存在
if(ret) //If the access function returns -1, the file does not exist
{
int export_file_fd, len;
char export_file_path[100] = {
0};
strcpy(export_file_path, Y_GPIO_PATH);
strcat(export_file_path, “/”);
strcat(export_file_path, Y_GPIO_OPERATIONS[GPIO_EXPORT]);
export_file_fd = open(export_file_path, O_WRONLY);
if(0 > export_file_fd)
{
perror(“export_file open error!!!\r\n”);
exit(-1);
}
len = strlen(argv[1]);
if(len != write(export_file_fd, argv[1], len))
{
perror(“export_file write error!!!\r\n”);
close(export_file_fd);
exit(-1);
} close(export_file_fd);
}
y_gpio_config(file_path, Y_GPIO_CFG[GPIO_CFG_DIRECTION], Y_GPIO_DIRECTION_MODE[GPIO_DIRECTION_OUT]);</span y_gpio_config(file_path, Y_GPIO_CFG[GPIO_CFG_ACTIVE_LOW], Y_GPIO_ACTIVE_LOW_CTRL[GPIO_ACTIVE_LOW_ZERO_MEAN_LOW]);
int seconds = 0;
while(seconds < 60)
{
y_gpio_config(file_path, Y_GPIO_CFG[GPIO_CFG_VALUE], Y_GPIO_VALUE_TYPE[GPIO_VALUE_LOW]);
sleep(1);
y_gpio_config(file_path, Y_GPIO_CFG[GPIO_CFG_VALUE], Y_GPIO_VALUE_TYPE[GPIO_VALUE_HIGH]);
sleep(1);
printf(“The %d seconds.\r\n”, seconds += 2);
} exit(0);
}
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include "gpio_output.h" /* variables */ const char *const Y_GPIO_OPERATIONS[OPERATION_COUNT] = {"export","unexport"}; const char *const Y_GPIO_CFG[CFG_COUNT] = {"active_low","direction","edge","uevent","value"}; const char *const Y_GPIO_ACTIVE_LOW_CTRL[ACTIVE_LOW_COUNT] = {"0","1"}; const char *const Y_GPIO_DIRECTION_MODE[DRIECTION_COUNT] = {"out","in"}; const char *const Y_GPIO_EDGE_MODE[EDGE_COUNT] = {"none","rising","falling","both"}; const char *const Y_GPIO_VALUE_TYPE[VALUE_COUNT] = {"0","1"}; const char *const Y_GPIO_PATH = "/sys/class/gpio"; const char *const Y_GPIO_OUTPUT_APP = "./gpioAPP"; const char *const Y_GDB_GPIO_OUTPUT_APP = "./gdb_gpioAPP"; /* functions */ void y_gpio_config(const char *dir, const char *cfg, const char *val) { int cfg_fd, cfg_len; char file_cfg[100] = {0}; strcpy(file_cfg, dir); strcat(file_cfg, "/"); strcat(file_cfg, cfg); cfg_fd = open(file_cfg, O_WRONLY); if(0 > cfg_fd) { printf("gpio %s file open error!!!\r\n", cfg); exit(-1); } cfg_len = strlen(val); if(cfg_len != write(cfg_fd, val, cfg_len)) { printf("gpio %s %s file write error!!!\r\n", cfg, val); close(cfg_fd); exit(-1); } close(cfg_fd); }
#ifndef __GPIO_OUTPUT_ #define __GPIO_OUTPUT_ /* enum */ typedef enum{ GPIO_EXPORT = 0, GPIO_UNEXPORT = 1, OPERATION_COUNT, }GPIO_OPERATIONS; typedef enum{ GPIO_CFG_ACTIVE_LOW = 0, GPIO_CFG_DIRECTION = 1, GPIO_CFG_EDGE = 2, GPIO_CFG_UEVENT = 3, GPIO_CFG_VALUE = 4, CFG_COUNT, }GPIO_CONFIG; typedef enum{ GPIO_ACTIVE_LOW_ZERO_MEAN_LOW = 0, GPIO_ACTIVE_LOW_ZERO_MEAN_HIGH = 1, ACTIVE_LOW_COUNT, }GPIO_ACTIVE_LOW_CTRL; typedef enum{ GPIO_DIRECTION_OUT = 0, GPIO_DIRECTION_IN = 1, DRIECTION_COUNT, }GPIO_DIRECTION_MODE; typedef enum{ GPIO_EDGE_NONE = 0, GPIO_EDGE_RISING = 1, GPIO_EDGE_FALLING = 2, GPIO_EDGE_BOTH = 3, EDGE_COUNT, }GPIO_EDGE_MODE; typedef enum{ GPIO_VALUE_LOW = 0, GPIO_VALUE_HIGH = 1, VALUE_COUNT, }GPIO_VALUE_TYPE; /* variables extern */ extern const char *const Y_GPIO_OPERATIONS[OPERATION_COUNT]; extern const char *const Y_GPIO_CFG[CFG_COUNT]; extern const char *const Y_GPIO_ACTIVE_LOW_CTRL[ACTIVE_LOW_COUNT]; extern const char *const Y_GPIO_DIRECTION_MODE[DRIECTION_COUNT]; extern const char *const Y_GPIO_EDGE_MODE[EDGE_COUNT]; extern const char *const Y_GPIO_VALUE_TYPE[VALUE_COUNT]; extern const char *const Y_GPIO_PATH; extern const char *const Y_GPIO_OUTPUT_APP; extern const char *const Y_GDB_GPIO_OUTPUT_APP; /* functions extern */ extern void y_gpio_config(const char *dir, const char *cfg, const char *val); #endif
2.2 编写各级目录下的CMakeLists.txt文件
从我们的目录结构可以看出,我们需要编写三份CMakeLists.txt文件。
分别是:
1.工程目录16_gpio下的顶层CMakeLists.txt,
2.16_gpio/libbsp下的CMakeLists.txt,
3.16_gpio/src下的CMakeLists.txt,三份CMakeLists.txt内容如下
工程目录16_gpio下的顶层CMakeLists.txtcmake_minimum_required(VERSION 3.5) project(gdb_gpioAPP) add_subdirectory(libbsp) add_subdirectory(src)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) add_library(libbsp gpio_output.c) set_target_properties(libbsp PROPERTIES OUTPUT_NAME "gdb_gpioAPP")
16_gpio/src下的CMakeLists.txt
include_directories(${PROJECT_SOURCE_DIR}/libbsp) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) add_executable(gdb_gpioAPP main.c) target_link_libraries(gdb_gpioAPP libbsp)
2.3 编写arm-linux-setup.cmake配置文件
上面我们准备好了各级目录下的CMakeLists.txt。这里还需要准备一份配置文件——> arm-linux-setup.cmake;
因为默认情况下cmake是使用Ubuntu系统的编译器来编译我们的工程,得到的可执行文件只能在Ubuntu系统上运行,所以我们需要利用配置文件来设置交叉编译以及设置支持gdb+gdbserver调试,这样可执行文件才可以在ARM开发板上运行。配置文件放在工程根目录下,内容如下
arm-linux-setup.cmakeset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots) set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi) set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g++) set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7") set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) ########################################################################################## #if you want to support the GDB Debug, you need to configure the following settings. set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -o0 -Wall -g -ggdb") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -o3 -Wall") ###########################################################################################
可以看到配置文件最底下有一段添加注释说明的代码,这几个配置项用于cmake支持GDB调试;
如果不需要调试的话注释掉即可,非常方便2.4 用camke工具构建生成Makefile
下面执行cmake命令时,指定了配置文件给cmake,让它能够配置交叉编译环境和GDB调试,并生成一份Makefile文件;
~/linux/tool/cmake-3.16.0-Linux-x86_64/bin/cmake -DCMAKE_TOOLCHAIN_FILE=../arm-linux-setup.cmake ..
-DCMAKE_TOOLCHAIN_FILE 选项用于指定配置文件,“=”号后面的内容是它的值,也就是它指定的配置文件2.5 make编译工程+拷贝可执行文件
我们得到了Makefile文件后,用make命令编译工程,编译完毕后输入下面命令拷贝可执行文件到I.MX6ULL ARM 开发板上
cp bin/gdb_gpioAPP /home/yxm/linux/nfs/rootfs/bin
2.6 VSCode+GDB调试配置
使用VSCode+gdbserver图形化调试嵌入式Linux C应用程序之前,需要根据实际情况配置launch.json文件;
我的launch.json配置内容如下:{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "gdb_gpioAPP", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/bin/gdb_gpioAPP", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/build/bin", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "miDebuggerPath": "/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gdb", "miDebuggerServerAddress": "192.168.0.25:2001" } ] }
三、实验测试
3.1 运行 cmake + make 编译生成可执行文件
编译完成工程之后,如果再次执行cmake,会提示一个CMake WarningCMake Warning: Manually-specified variables were not used by the project: CMAKE_TOOLCHAIN_FILE
这是因为缓存没变化,再次配置的时候出现错误;这时候把先前编译生成的文件缓存清理干净,重新cmake之后就正常了
3.2 运行实验
如图所示,程序运行到main.c的断点
如图所示,程序运行到gpio_output.c的断点
程序运行结束