gdb+gdbserver远程调试

gdb+gdbserver远程调试

 

在开发机器上:

  • 安装 VS Code Insiders, 在 2019年05月, 只有这个版本支持 Remote - Development 插件
  • 安装 VSCode 扩展 “Remote - Development”, 方法是左下角 齿轮图标 -> 扩展, 直接搜索商店
  • 安装兼容 OpenSSH 的 SSH 客户端, 对于 Windows 7, 安装 Git for Windows 即可, Windows 10 的选择更多

在执行机器上:

  • 安装 gdb, 对于 CentOS 是 yum install gdb
  • 安装 gdbserver, 对于 CentOS 是 yum install gdb-gdbserver
  • 启动 ssh 服务端, 一般都有

使用 VSCode 远程访问代码

以前的办法是通过 sftp, samba 等协议, 把 Linux 环境的文件映射到 Windows 做成一个虚拟盘, 这类工具有很多, 可以选 RaiDrive 等等, 现在 VSCode 的官方扩展 “Remote - Development” 可以直接走 ssh 协议访问远程文件, 就不必映射虚拟盘了

安装好上述扩展, 首先改设置, 左下角 齿轮图标 -> 设置, 搜 “remote.SSH”, 勾选 remote.SSH.showLoginTerminal (或者直接在 settings.json 里添加一行 "remote.SSH.showLoginTerminal": true 也可)

完后就可以连接到远程机器了, 在 VSCode 主界面 ctrl+shift+p 选 Remote.SSH: Connect to host, 输入 root@<ip>, 如果是密码认证, 之后留意终端窗口, 在里面输入密码

首次连接会多次要求输入密码, 成功后界面左下角会有 SSH: <ip> 的已连接状态, 同时这个 VSCode 也变成了该远程连接的专属实例, 然后就可以从侧边栏打开项目路径了, 打开后如图:

图示通过 SSH 连接后的远程 VSCode 项目

之后根据项目类型, 可以安装一些基本的扩展, 如:

  • C/C++ IntelliSense, debugging, and code browsing
  • autoconf
  • Bookmarks

等等, 注意有些扩展是要安装在 SSH 的目标机器上, 安装时会提示你 install on SSH: xxxx, 而主题类扩展和一部分功能类扩展是安装在本地机器上

如果 Remote-SSH 连接到远程机器是密码认证的, 频繁使用时每次都要输密码, 很麻烦, 这时用公钥认证更好, 这里参考 Windows 下 VS Code Remote-Development(ssh) 插件的安装和配置 基本思路就是典型的 SSH 密钥方式登录: 生成一对公钥私钥, Linux 执行环境安装公钥, VSCode 使用私钥, 如下:

首先生成密钥:

# 生成一对密钥, 在哪个机器生成都行
ssh-keygen -t rsa

# 公钥需要放到 Linux 执行环境里
# 假设生成的公钥是 "vscode_rsa.pub"
cat /root/.ssh/vscode_rsa.pub >> /root/.ssh/authorized_keys
chmod 644 /root/.ssh/authorized_keys

# 私钥需要留在 VSCode 一端 Windows 机器里
# 假设路径是 "D:/.ssh/vscode_rsa"

然后在 VSCode 主界面 ctrl+shift+p 选 Remote.SSH: Open Configuration File, 输入以下内容:

Host dev-56144               <- 连接标识, 随便写
    HostName 172.16.xx.xxx           <- 远程机器 IP
    User root
    IdentityFile D:/.ssh/vscode_rsa  <- 私钥的本地完整路径

配置好后, 在 Remote.SSH: Connect to host 时就会看到这个连接配置了

使用 VSCode 和 gdbserver 远程调试 C 代码

在远程 Linux 机器上, 运行 gdbserver localhost:<port> <program> <args>, 比如

gdbserver localhost:2333 /path/to/myprogram myarg1 myarg2 myarg3

这里假设远程 Linux 机器 IP 是 172.16.56.144, gdbserver 的端口设为 2333, 在 VSCode 机器上, 进入菜单 调试 -> 添加配置, 会生成一个配置文件 SSH之后的项目根目录/.vscode/launch.json, 将其修改如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "gdb Remote Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "/path/to/myprogram",
            "args": ["myarg1", "myarg2", "myarg3"],
            "stopAtEntry": true,
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "miDebuggerArgs": "gdb",
            "linux": {
                "MIMode": "gdb",
                "miDebuggerPath": "/usr/bin/gdb",
                "miDebuggerServerAddress": "172.16.56.144:2333",
            },
            "logging": {
                "moduleLoad": false,
                "engineLogging": false,
                "trace": false
            },
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "cwd": "${workspaceFolder}",
        }
    ]
}

几个注意的地方:

  • 应该是 "request": "launch", 不是 “attach”, 此后也并不需要记录进程ID
  • 需要填对 "miDebuggerServerAddress": "172.16.56.144:2333", 有这个设置才会开启 gdb 远程调试
  • "engineLogging": true 可以看到 gdb 自身的详细消息
  • 必须是 "externalConsole": false 否则报错
  • /path/to/myprogram 应该是在 gdbserver 和 launch.json 里都要填一次

之后就可以正常使用调试功能了, 添加断点, 监视等等, 如图:

图示远程 gdb 调试

在 VSCode 的调试控制台输入变量名, 就可以直接打印出来, 想使用默认的调试器命令要在前面加 -exec, 如 -exec p/x args

附 gdbserver usage

gdbserver  --help
Usage:    gdbserver [OPTIONS] COMM PROG [ARGS ...]
    gdbserver [OPTIONS] --attach COMM PID
    gdbserver [OPTIONS] --multi COMM

COMM may either be a tty device (for serial debugging), or
HOST:PORT to listen for a TCP connection.

Options:
  --debug               Enable general debugging output.
  --remote-debug        Enable remote protocol debugging output.
  --version             Display version information and exit.
  --wrapper WRAPPER --  Run WRAPPER to start new programs.
  --once                Exit after the first connection has closed.
Report bugs to "<http://www.gnu.org/software/gdb/bugs/>".

 

1.编译时加-g

2.gdbserver + gdb 远程调试
prebuilts/toolchains/mips-gcc720-glibc226/mips-linux-gnu/libc/mfp64/usr/bin/gdbserver 
把它拷贝到target

3.target
gdbserver 宿主机 IP:端口号 要调试的可执行程序
#./gdbserver 192.168.0.240:50555 ./cloud_sound_dance

4.host
mips-linux-gnu-gdb 要调试的可执行程序
#mips-linux-gnu-gdb tmp_files/cloud_sound_dance

target remote 目标板 IP:端口号
(gdb) target remote 192.168.0.174:50555

5.设置断点,运行:
(gdb) b App.cpp:308
Breakpoint 1 at 0x454834: file ../App.cpp, line 308.
(gdb) r
The "remote" target does not support "run".  Try "help target" or "continue".
(gdb) continue
Continuing.

(gdb) info breakpoints

 

命令

命令缩写

命令说明

list

l

显示多行源代码

break

b

设置断点,程序运行到断点的位置会停下来

info

i

描述程序的状态

run

r

开始运行程序

display

disp

跟踪查看某个变量,每次停下来都显示它的值

step

s

执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句

next

n

执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)

print

p

打印内部变量值

continue

c

继续程序的运行,直到遇到下一个断点

set var name=v

 

设置变量的值

start

st

开始执行程序,在main函数的第一条语句前面停下来

file

 

装入需要调试的程序

kill

k

终止正在调试的程序

watch

 

监视变量值的变化

backtrace

bt

产看函数调用信息(堆栈)

frame

f

查看栈帧

quit

q

退出GDB环境

 

对于软件开发来说,调试程序是比不可少的。对于开发PC软件通常系统已经继承了调试工具(比如Linux系统的GDB),或者IDE直接支持对程序的调试。而对于开发嵌入式软件来说调试的手段比较有限,很多开发者仅有的调试手段依然是最原始的打印(我也是其中之一)。当然除了打印调试之外还有通过gdb+gdbserver来调试,gdbserver在目标系统中运行,gdb则在宿主机上运行。

一、源码下载

对于嵌入式软件开发调试工具没有现成的,且嵌入式系统比较繁杂,gdbserver需要根据目标系统单独编译。gdb的源码包下载地址为:http://ftp.gnu.org/gnu/gdb/。目前最新的版本为8.0但由于8.0加入了C++11,而我的目标系统的交叉工具链不支持C++11,故下载7.12版本。

二、编译arm-linux-gdb

Linux系统本身已经自带gdb工具,但无法用在嵌入式调试中,需要单独编译arm-linux-gdb
  
1.解压源码包

$ tar zxvf gdb-7.12.tar.gz 
$ cd gdb-7.12/

2.生成Makefile

$ ./configure --target=arm-linux --prefix=$PWD/__install

–target:指定目标平台。–prefix:指定安装路径。
 
3.编译

$ make

4.安装

$ make install

执行此命令后会在当前目录下生成文件夹__install/里面包含可执行文件、头文件、动态库文件等,如下图所示:

这里写图片描述
目前只用到bin/目录下的可执行文件arm-linux-gdb,执行下面命令:

$ sudo cp __install/bin/arm-linux-gdb /usr/bin/

将生成的arm-linux-gdb文件拷贝到系统/usr/bin/目录下,这样便可以在任何地方很方便的调用。

三、编译gdbserver

1.生成Makefile

$ cd gdb/gdbserver/
$ ./configure --target=arm-linux --host=arm-linux-gnueabi

–host:指定交叉工具链,arm-linux-gnueabi为我的目标系统的交叉工具链。
  
2.编译

$ make

编译gdbserver不需要执行make install命令,make之后在当前目录下会生成可执行程序gdbserver,将其拷贝到目标系统中。

四、可能出现的问题

1.GDB7.6 Remote ‘g’ packet reply is too long错误

在gdb调试过程中可能出现如下图所示错误:
这里写图片描述

解决方案:

修改gdb/remote.c文件,屏蔽process_g_packet函数中的下列两行:

 if (buf_len > 2 * rsa->sizeof_g_packet)
     error (_(“Remote ‘g’ packet reply is too long: %s”), rs->buf);

在其后添加:

 if (buf_len > 2 * rsa->sizeof_g_packet) {
      rsa->sizeof_g_packet = buf_len ;
      for (i = 0; i < gdbarch_num_regs (gdbarch); i++)
      {
         if (rsa->regs[i].pnum == -1)
         continue;

         if (rsa->regs[i].offset >= rsa->sizeof_g_packet)
         rsa->regs[i].in_g_packet = 0;
         else
         rsa->regs[i].in_g_packet = 1;
      }
   }

2.arm-linux-gdb加载目标系统库出错

arm-linux-gdb在调试的时候会加载目标系统的库文件,如果出错时便无法调试,如下图所示:
这里写图片描述

解决方案

可通过指令[set solib-search-path+库文件路径]来手动加载目标系统库文件,如下为我的设置:

(gdb) set solib-search-path /home/zxd/MerriiLinux_qa1_qe1/boot/config/gcc-linaro/arm-linux-gnueabi/libc/lib/

五、调试

下面给出调试用的示例代码:

test.c

#include <stdio.h>

int main(void)
{
	int i;
	for (i=0; i<5; i++) {
		printf("Hello World:%d\n", i);
	}
	
	printf("Good bye!\n");

	return 0;
}

1.编译用于调试的程序

$ arm-linux-gnueabi-gcc -g test.c -o test

为了能够gdb调试,在编译程序的时候必须加-g选项,将生成的可执行文件test拷贝到目标板中。

2.环境配置

宿主机IP:192.168.0.139
目标板IP:192.168.0.138

3.目标板上运行gdbserver

# cd /
# gdbserver 192.168.0.139:6666 /test 

其中 192.168.0.139 是宿主机的地址,6666是调试端口,helloword是需要调试的可执行程序。gdbserver启动之后打印出下面内容:
这里写图片描述

4.宿主机上运行arm-linux-gdb

$ arm-linux-gdb test

arm-linux-gdb启动之后会有如下打印:
这里写图片描述
其中最后一行(gdb) 表示arm-linux-gdb在等待输入指令,现在需要输入指令来连接gdbserver,如下所示:

(gdb) target remote 192.168.0.138:6666

输入上述指令若成功连接目标板会打印出下图所示:
这里写图片描述

宿主机会打印出如下图所示:
这里写图片描述

5.指定库文件路径

由上图可知arm-linux-gdb加载目标系统库文件成功,如果失败需要手动加载,下面给出对于我的目标板的指令:

(gdb) set solib-search-path /home/zxd/MerriiLinux_qa1_qe1/boot/config/gcc-linaro/arm-linux-gnueabi/libc/lib/

6.调试
  下面给出调试结果,具体调试方法跟PC机上gdb调试类似,只是在设置断点后,gdb通过run/r指令让程序运行。而对于嵌入式系统来说程序已经通过gdbserver运行了,arm-linux-gdb通过continue/c让程序继续运行,下面为调试结果:
这里写图片描述

其中左侧的为目标开发板通过串口终端打印出来的,右边窗口为通过ssh连接宿主机的终端。

 

segmentation-fault段错误一直是一个很难解决的问题,尤其是当代码量很大的时候寻找起来更是如大海捞针一般,本文将介绍通过gdb+gdbserver来找到出错的位置。

segmentation-fault段错误有时候每次都能重现出来,这种问题相对来说比较好调试可以直接在线调试。有的问题比较难重现,甚至可能运行好几天才有可能重现,这种问题一般是通过分析segmentation-fault段错误产生的core文件。

下面给出本文用的示例代码:
test.c

#include <stdio.h>  
#include <string.h>

int main()  
{  
	char* p;

	memcpy(p, "hello", 5);

    return 0;  
} 

用gdb调试代码时必须加上-g选项,如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。先对示例代码交叉编译到目标机上:

arm-linux-gnueabi-gcc -g test.c -o test

将生成的可执行程序test拷贝到目标机上便可以开始本次试验了。

一、直接在线调试

1.在目标机上启动gdbserver

$ gdbserver 192.168.0.139:6666 /test 

这里写图片描述

2.在宿主机上启动gdb

$ arm-linux-gdb test

这里写图片描述

3.连接目标机

(gdb) target remote 192.168.0.138:6666

若连接成功会有下面提示:
这里写图片描述

在目标机上也有连接成功提示:
这里写图片描述

4.调试

执行指令 c(continue)让程序开始执行并出错,若错误简单可直接显示出错误的具体位置:
这里写图片描述

可以通过指令bt来查看当前的堆栈信息:
这里写图片描述

若程序比较复杂,出错时没有显示出具体的出错信息,可通过where指令来查看出错的位置:
这里写图片描述

二、抓取core文件

1.系统设置

Linux系统默认core文件的大小限制为0,即产生segmentation-fault段错误时不会生成core文件,可以通过ulimit -c指令来查看系统限制。可以通过ulimit -c filesize命令来修改系统对core文件大小的限制,但如果core文件超过限制大小(filesize的单位为kbyte)将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。所以一般情况下不会限制core文件的大小,指令如下:

$ ulimit -c unlimited

2.产生core文件

当运行程序出现segmentation-fault段错误时,如果产生core文件,会在终端有如下打印:
这里写图片描述

在当前目录下也会生成core文件:
这里写图片描述

3.调试core文件

core文件产生之后将其拷贝到宿主机,不需要连接目标机便可调试。运行下列指令启动gdb:

$ arm-linux-gdb test core

这里写图片描述

由于本文采用的示例比较简单,gdb已经列出了出错的具体位置,敲击回车,gdb列出了具体出错的代码:
这里写图片描述

可以通过指令bt来查看堆栈信息:
这里写图片描述

可以通过where指令来定位出错的位置:
这里写图片描述

 

=============

linux - 加载核心文件时,gdb 不会加载共享库符号,甚至 libc.so (musl)

https://stackoverflow.com/questions/59913495/

我正在尝试调试在具有 MIPS cpu 的板上远程运行的程序,使用 musl 作为其 libc。
如果我在板上启动 gdbserver,通过 set sysroot /path/to/sysroot 设置 sysroot 并从 gdb 实时连接,我会得到一个有意义的堆栈跟踪(由于 musl 在 MIPS 上缺少 CFI 指令而我不得不添加它们,这需要数小时的努力,但这是一个单独的问题),我可以看到 gdb 从 sysroot 的 libc.so 加载符号。

另一方面,如果我让该程序崩溃并生成核心转储(我使用 kill -6 <pid> 来强制进行测试),gdb 将从二进制文件中加载符号,但不会加载其任何共享库,甚至不会加载 libc.so 。虽然其他共享库很好但不是必需的,但没有来自 libc.so 的调试信息,gdb 无法解析堆栈跟踪,它们看起来都像垃圾。

成功的实时 gdb session

$ mipsel-poky-linux-gdb -iex "set sysroot /path/to/sysroot" /path/to/testprog
GNU gdb (GDB) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pokysdk-linux --target=mipsel-poky-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /path/to/testprog...done.
(gdb) 
(gdb) target extended-remote remote-hostname:10000
Remote debugging using remote-hostname:10000
Reading symbols from /path/to/sysroot/lib/ld.so.1...done.
__cp_end () at src/thread/mips/syscall_cp.s:38
38      beq     $7, $0, 1f
(gdb) bt
#0  __cp_end () at src/thread/mips/syscall_cp.s:38
#1  0x77eff348 in __syscall_cp_c (nr=4029, u=<optimized out>, v=<optimized out>, w=<optimized out>, x=0, y=0, z=0)
    at src/thread/pthread_cancel.c:33
#2  0x77f0b4b0 in pause () at src/unistd/pause.c:7
#3  0x556ecd3c in core_run (argc=1, argv=0x7f7bf4a4) at /path/to/source-file.cpp:461
#4  0x77e93d28 in libc_start_main_stage2 (main=0x556b1ac0 <main(int, char**)>, argc=1, argv=0x7f7bf4a4)
    at src/env/__libc_start_main.c:94
#5  0x556eb890 in _start_c (p=<optimized out>) at crt/crt1.c:18
#6  0x556eb850 in _start () at /path/to/header-file.hpp:130
Backtrace stopped: frame did not save the PC


(注意:在上面我用占位符替换了内部路径/文件名/等)

不成功的核心转储 gdb session
$ mipsel-poky-linux-gdb -iex "set verbose on" -iex "set sysroot /path/to/sysroot" /path/to/testprog core
GNU gdb (GDB) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pokysdk-linux --target=mipsel-poky-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /path/to/testprog...done.
Reading in symbols for /path/to/source/main.cpp...done.
[New LWP 1285]
[New LWP 1408]
[New LWP 1409]
[New LWP 1410]
[New LWP 1412]
[New LWP 1407]
[New LWP 1401]
[New LWP 1402]
[New LWP 1403]
[New LWP 1404]
Using PIE (Position Independent Executable) displacement 0x555c4000 for "/path/to/testprog".

warning: platform-specific solib_create_inferior_hook did not load initial shared libraries.
Reading symbols from system-supplied DSO at 0x7ff2b000...(no debugging symbols found)...done.
Core was generated by `/remote/path/to/testprog'.
Program terminated with signal SIGABRT, Aborted.
#0  0x77e80204 in ?? ()
[Current thread is 1 (LWP 1285)]
(gdb) bt
warning: GDB can't find the start of the function at 0x77e80204.

    GDB is unable to find the start of the function at 0x77e80204
and thus can't determine the size of that function's stack frame.
This means that GDB may be unable to access that stack frame, or
the frames below it.
    This problem is most likely caused by an invalid program counter or
stack pointer.
    However, if you think GDB should simply search farther back
from 0x77e80204 for code which looks like the beginning of a
function, you can increase the range of the search using the `set
heuristic-fence-post' command.
#0  0x77e80204 in ?? ()
(gdb)

(注意:在上面我用占位符替换了内部路径/文件名/等)

我尝试过的事情

我尝试了几件事,包括使用 set solib-search-path 而不是 set sysroot ,直接告诉 gdb 通过 add-symbol-file /path/to/libc.so 甚至 add-symbol-file /path/to/libc.so 0xdeadbeef 加载库,其中 0xdeadbeef 实际上是加载库的地址,通过 readelf 获得。在最后一种情况下,gdb 最终加载了符号,但显然有些地方不对劲,可能我传递的地址不正确。问题是,我不应该这样做,gdb 应该在核心转储中找到这些信息并加载库!我如何让它做到这一点,为什么它一开始不这样做呢?

 

最佳答案

给 /path/to/libc.sogdb它显示了什么?它可以读取调试符号吗?$ gdb /path/to/libc.so
一般libc.so不包含调试符号本身,而是包含指向包含其调试符号的文件的链接

在 Linux 中,调试符号可以位于:
/usr/lib/debug/usr/bin/.debug
看看gdb在哪里正在寻找单独的调试符号文件:
(gdb) show debug-file-directory
如果需要,如果包含所需的调试符号文件,您可以设置为另一个

您可以知道 .so 指向的调试符号文件的名称,如下所示:
$ readelf -x.gnu_debuglink /lib/x86_64-linux-gnu/libc.so.6

Hex dump of section '.gnu_debuglink':
  0x00000000 6c696263 2d322e32 372e736f 00000000 libc-2.27.so....
  0x00000010 48c02c04                            H.,.

libc-2.27.so是调试符号文件的名称,它实际上在 /usr/lib/debug//lib/x86_64-linux-gnu/ 中在我的系统上

或者,
add-symbol-file /path/to/libc.so #这里你应该给出包含调试符号的文件,而不是只包含调试符号文件链接的文件,因为它不包含路径(见上面的输出),调试符号文件是在帮助下找到的,使用debug-file-directory

 

=============== End

 

posted @ 2023-06-12 08:27  lsgxeva  阅读(2113)  评论(0编辑  收藏  举报