1.openbmc 基于qemu的码调调试环境搭建
2.OpenOCD代码结构浅析(基于RISCV)
3.C语言调试的作用C语言调试器是如何工作的
4.C/C++ 集成内存调试、内存泄漏检测和性能分析的码调工具 Valgrind Linux 下 Valgrind 工具的全面使用指南
5.VScode配置MASM32运行环境(断点/运行/debug/配合emu8086(非DOSBox))
openbmc 基于qemu的调试环境搭建
基础知识略过,本文聚焦于openbmc开发调试的码调核心部分——前后端联动单步调试,将全面展示搭建基于qemu的码调调试环境。
搭建环境前,码调确保基础环境准备就绪,码调java ssm 项目源码openbmc开发者通常具备所需基础知识。码调首先,码调下载SDK手册,码调选用ASpeed芯片作为典型例子,码调多数openbmc项目采用此版本。码调
推荐使用自定义脚本辅助编译过程,码调自行试验后发现效果显著。码调成功编译后,码调即完成基础环境搭建。码调接下来,转向前后端调试环境的构建。
使用qemu核心参数实现主机与虚拟机间端口转发,此操作相当于提供一块虚拟开发板,使得外部访问变得简单直接。主机端口转发命令示例为:hostfwd=[tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport。此选项支持针对TCP或UDP协议的数据传输,且允许在单个命令中指定多个端口转发。
注意系统默认apt安装的版本为6.2,过时可能导致模拟运行失败。需进行升级操作。通过命令删除旧版本,并下载openbmc发布的8.2版本,确保模拟环境的兼容性。
前端UI运行与后端运行同步进行。通过qemu启动openbmc镜像,调整相关参数,确保与自身环境相适应。针对romulus测试镜像和ast,分别通过bitbake编译生成最新的(V.)版本,并增加gdb调试端口转发至主机端口。
前端代码准备阶段,openbmc前端已采用vue实现(vue2),webui-vue代码通过下载获得。老版本UI已不再维护,建议基于AngularJS的代码不再考虑。Node版本推荐使用。
项目文件修改涉及增加环境变量,可通过修改webui-vue中的什么是源码空间配置文件vue.config.js完成,其中ip地址为Ubuntu宿主机的ip地址和转发端口。
项目运行阶段,使用vscode打开项目,并在edge中安装Vue开发者工具。前端效果验证通过后,应能通过前端链接访问到qemu中openbmc的web后端——bmcweb,用户名和密码默认为root/0penBmc,初次访问需确认风险继续。
VUE开发者工具的集成使得调试更加直观有效。后端调试方面,通过yocto的开发者工具devtool进行代码导出,这是整个openbmc作为大型Linux发布系统集成的体现。建议掌握两个基础命令,更详细的命令参考可获取。
源码编译阶段,推荐通过标准SDK进行,而非增量编译。标准SDK编译过程可控,参数调整方便。导出标准SDK后,无yocto环境的主机也可调试openbmc固件,下载配套源码进行编译。
bmcweb更新通过scp命令上传编译好的带debuginfo的版本,注意需先stop服务/kill相关进程,确保上传成功后再次启动服务。gdbserver交叉编译与安装则用于gdb调试,启动qemu时增加gdb调试端口转发至主机端口。通过gdbserver与宿主机连接,实现调试。
调试demo以获取NTP信息页面为例,展示调试流程。总结而言,通过以上步骤搭建的gdb调试环境适用于复杂如bmcweb后端的案例,其他dbus应用程序亦可基于此方法进行调试,核心要点在于掌握gdb调试技巧。相信有了gdb,openbmc的学习与理解将更深入。
OpenOCD代码结构浅析(基于RISCV)
揭示OpenOCD的RISCV调试之旅:深度解析与实践在探索RISC-V平台的JTAG模块调试世界中,OpenOCD代码的精髓成为了一道迷人的学习课题。OpenOCD的架构犹如精密的调音器,由两大部分巧妙地编织:配置文件的解析与GDB通信的桥梁。
配置文件解析艺术OpenOCD的起始点是配置文件,这里隐藏着TCL语法的java数据脱敏源码魔法。command_registrants[]数组如同指挥家的指挥棒,引导预注册的handler函数,如trace handler,以独特的名字定位,灵活适应不同的工作模式。每个handler函数注册后,它们形成一个有序的执行矩阵,便于Jim解释器高效地搜索并执行命令。
GDB通信的秘密通道服务器的核心是server_loop(),它如同信使,接收socket中的每一道指令,无论是设置断点还是执行其他操作。设置软件断点的奥秘,是通过riscv_remove_breakpoint函数,将OpenOCD的机器码巧妙地“写回”到目标MCU的内存地址。底层操作涉及dmi_write()和dmi_read(),犹如在调试的迷宫中穿行,通过Debug Module获取和修改内存。
OpenOCD通过DTM寄存器深入RISC-V的CSR世界,利用DMI命令格式进行抽象操作,实现对mstatus等寄存器的间接访问。异常处理流程中,每一步都像一场精密的舞蹈,信号通过JTAG的TCK、TMS、TDI和TDO四根引脚交织传递。 调试实战指南要驾驭OpenOCD,首先得铺好基础:安装依赖、下载源码、配置ddd调试器,编译并启动gdbserver和ddd,熟练运用gdb的断点、单步命令。DDD的可视化工具如变量查看、调用栈和寄存器窗口,让调试过程更加直观。
理解OpenOCD与处理器的亲密合作至关重要:GDB加载elf文件,通过符号信息驱动,OpenOCD则搭建起GDB与Debug-Module之间的沟通桥梁,实现精准的调试操作。 探索之旅的下一步对OpenOCD源码的深入研究,是了解其精髓的关键,这是隐形暗码溯源码一场永无止境的学习之旅。未来,我将继续学习,更新文档,以更全面的视角解析OpenOCD的复杂运作。
C语言调试的作用C语言调试器是如何工作的
C语言调试的作用,C语言调试器是如何工作的很多人还不知道,现在让我们一起来看看吧!
C语言调试器是如何工作的
当你用GDB 的时候,可以看到它完全控制了应用程序进程。当你在程序运行的时候用 Ctrl + C,程序的运行就能够终止,而GDB能展示它的当前地址、堆栈跟踪信息之类的内容。你知道C语言调试器是如何工作的吗?下面是小编为大家带来的关于C语言调试器是如何工作的的知识,欢迎阅读。
但是它们怎么不工作呢?
开始,让我们先研究它怎样才会不工作。它不能通过阅读和分析程序的二进制信息来模拟程序的运行。它其实能做,而那应该能起作用(Valgrind 内存调试器就是这样工作的),但是这样的话会很慢。Valgrind会让程序慢倍,但是GDB不会。它的工作机制与Qemu虚拟机一样。
所以到底是怎么回事?黑魔法?……不,如果那样的话就太简单了。
另一种猜想?……?破解!是的,这里正是这样的。操作系统内核也提供了一些帮助。
首先,关于Linux的进程机制需要了解一件事:父进程可以获得子进程的附加信息,也能够ptrace它们。并且你可以猜到的是,调试器是被调试的进程的父进程(或者它会变成父进程,在Linux中进程可以将一个进程变为自己子进程:-))
Linux Ptrace API
Linux Ptrace API 允许一个(调试器)进程来获取低等级的其他(被调试的)进程的信息。特别的,这个调试器可以:
读写被调试进程的内存 :PTRACE_PEEKTEXT、PTRACE_PEEKUSER、PTRACE_POKE……
读写被调试进程的CPU寄存器 PTRACE_GETREGSET、PTRACE_SETREGS
因系统活动而被提醒:PTRACE_O_TRACEEXEC, PTRACE_O_TRACECLONE, PTRACE_O_EXITKILL, PTRACE_SYSCALL(你可以通过这些标识区分exec syscall、clone、exit以及其他系统调用)
控制它的执行:PTRACE_SINGLESTEP、PTRACE_KILL、如何抠溯源码PTRACE_INTERRUPT、PTRACE_CONT (注意,CPU在这里是单步执行)
修改它的信号处理:PTRACE_GETSIGINFO、PTRACE_SETSIGINFO
Ptrace是如何实现的?
Ptrace的实现不在本文讨论的范围内,所以我不想进一步讨论,只是简单地解释它是如何工作的(我不是内核专家,如果我说错了请一定指出来,并原谅我过分简化:-))
Ptrace 是Linux内核的一部分,所以它能够获取进程所有内核级信息:
读写数据?Linux有copy_to/from_user。
获取CPU寄存器?用copy_regset_to/from_user很轻松(这里没有什么复杂的,因为CPU寄存器在进程未被调度时保存在Linux的struct task_struct *调度结构中)。
修改信号处理?更新域last_siginfo
单步执行?在处理器出发执行前,设置进程task结构的right flag(ARM、x)
Ptrace是在很多计划的操作中被Hooked(搜索 ptrace_event函数),所以它可以在被询问时(PTRACE_O_TRACEEXEC选项和与它相关的),向调试器发出一个SIGTRAP信号。
没有Ptrace的系统会怎么样呢?
这个解释超出了特定的Linux本地调试,但是对于大部分其他环境是合理的。要了解GDB在不同目标平台请求的内容,你可以看一下它在目标栈里面的操作。
在这个目标接口里,你可以看到所有C调试需要的高级操作:
struct target_ops
{
struct target_ops *beneath;
/* To the target under this one. */
const char *to_shortname;
/* Name this target type */
const char *to_longname;
/* Name for printing */
const char *to_doc;
/* Documentation. Does not include trailing
newline, and starts with a one-line descrip-
tion (probably similar to to_longname). */
void (*to_attach) (struct target_ops *ops, const char *, int);
void (*to_fetch_registers) (struct target_ops *, struct regcache *, int);
void (*to_store_registers) (struct target_ops *, struct regcache *, int);
int (*to__breakpoint) (struct target_ops *, struct gdbarch *,
struct bp_target_info *);
int (*to__watchpoint) (struct target_ops *,
CORE_ADDR, int, int, struct expression *);
}
普通的GDB调用这些函数,然后目标相关的组件再实现它们。(概念上)这是一个栈,或者一个金字塔:栈顶的是非常通用的,比如:
系统特定的Linux
本地或远程调试
调试方式特定的(ptrace、ttrace)
指令集特定的(Linux ARM、Linux x)
那个远程目标很有趣,因为它通过一个连接协议(TCP/IP、串行端口)把两台“电脑”间的执行栈分离开来。
那个远程的部分可以是运行在另一台Linux机器上的'gdbserver。但是它也可以是一个硬件调试端口的界面(JTAG) 或者一个虚拟的机器管理程序(比如 Qemu),并能够代替内核和ptrace的功能。那个远程根调试器会查询管理程序的结构,或者直接地查询处理器硬件寄存器来代替对OS内核结构的查询。
想要深层次学习这个远程协议,Embecosm 写了一篇一个关于不同信息的详细指南。Gdbserver的事件处理循环在这,而也可以在这里找到Qemu gdb-server stub 。
总结一下
我们能看到ptrace的API提供了这里所有底层机制被要求实现的调试器:
获取exec系统调用并从调用的地方阻止它执行
查询CPU的寄存器来获得处理器当前指令以及栈的地址
获取clone或fork事件来检测新线程
查看并改变数据地址读取并改变内存的变量
但是这就是一个调试器的全部工作吗?不,这只是那些非常低级的部分……它还会处理符号。这是,链接源程序和二进制文件。被忽视可能也是最重要的的一件事:断点!我会首先解释一下断点是如何工作的,因为这部分内容非常有趣且需要技巧,然后回到符号处理。
断点不是Ptrace API的一部分
就像我们之前看到的那样,断点不是ptrace API的一部分。但是我们可以改动内存并获取被调试的程序信号。你看不到其中的相关之处?这是因为断点的实现比较需要技巧并且还要一点hack!让我们来检验一下如何在一个指定的地址设置一个断点。
1、这个调试器读取(ptrace追踪)存在地址里的二进制指令,并保存在它自己的数据结构中。
2、它在这个位置写入一个不合法的指令。不管这个指令是啥,只要它是不合法的。
3、当被调试的程序运行到这个不合法的指令时(或者更准确地说,处理器将内存中的内容设置好时)它不会继续运行(因为它是不合法的)。
4、在现代多任务系统中,一个不合法的指令不会使整个系统崩溃掉,但是会通过引发一个中断(或错误)把控制权交回给系统内核。
5、这个中断被Linux翻译成一个SIGTRAP信号,然后被发送到处理器……或者发给它的父进程,就像调试器希望的那样。
6、调试器获得信号并查看被调试的程序指令指针的值(换言之,是陷入 trap发生的地方)。如果这个IP地址是在断点列表中,那么就是一个调试器的断点(否则就是一个进程中的错误,只需要传过信号并让它崩溃)。
7、现在,那个被调试的程序已经停在了断点,调试器可以让用户来做任何他/她想要做的事,等待时机合适继续执行。
8、为了要继续执行,这个调试器需要 1、写入正确的指令来回到被调试的程序的内存; 2、单步执行(继续执行单个CPU指令,伴随着ptrace 单步执行); 3、把非法指令写回去(使得这个执行过程下一次可以再次停止) ;4、让这个执行正常运行
很整洁,是不是?作为一个旁观的评论,你可以注意到,如果不是所有线程同时停止的话这个算法是不会工作的(因为运行的线程可能会在合法的指令出现时传出断点)。我不会详细讨论GDB是如何解决这个问题的,但在这篇论文里已经说得很详细了:使用GDB不间断调试多线程程序。简要地说,他们把指令写到内存中的其他地方,然后把那个指令的指针指向那个地址并单步执行处理器。但是问题在于一些指令是和地址相关的,比如跳转和条件跳转……
处理符号和调试信息
现在,让我们回到信号和调试信息处理。我没有详细地学习这部分,所以只是大体地说一说。
首先,我们是否可以不使用调试信息和信号地址来调试呢?答案是可以。因为正如我们看到过的那样,所有的低级指令是对CPU寄存器和内存地址来操作的,不是源程序层面的信息。因此,这个到源程序的链接只是为了方便用户。没有调试信息的时候,你看程序的方式就像是处理器(和内核)看到的一样:二进制(汇编)指令和内存字节。GDB不需要进一步的信息来把二进制信息翻译成CPU指令:
(gdb) x/x $pc # heXadecimal representation
0xc: 0x 0x 0xf 0xfd
0xc: 0xa8ec 0x8b 0x8be 0x
0xc: 0x 0x
(gdb) x/i $pc # Instruction representation
=> 0xc: push %r
0xc: push %r
0xc: push %r
0xc: push %r
0xc: mov %rsi,%r
0xc6b: push %rbp
0xc6c: mov %edi,%ebp
0xc6e: push %rbx
0xc6f: sub $0x3a8,%rsp
0xc: mov (%rsi),%rdi
现在,如果我们加上调试信息,GDB能够把符号名称和地址配对:
(gdb) $pc
$1 = (void (*)()) 0xc
你可以通过 nm -a $file 来获取ELF二进制的符号列表:
nm -a /usr/lib/debug/usr/bin/ls.debug | grep " main"
c T main
GDB还会能够展示堆栈跟踪信息(稍后会详细说),但是只有感兴趣的那部分:
(gdb) where
#0 write ()
#1 0xde3 in _IO_new_file_write ()
#2 0xde4c in new_do_write ()
#3 _IO_new_do_write ()
#4 0xd in _IO_new_file_overflow ()
#5 0xbb in print_current_files ()
#6 0xb in main ()
我们现在有了PC地址和相应的函数,就是这样。在一个函数中,你将需要对着汇编来调试!
现在让我们加入调试信息:就是DWARF规范下的gcc -g选项。我不是特别熟悉这个规范,但我知道它提供的:
地址到代码行和行到地址的配对
数据类型的定义,包括typedef和structure
本地变量和函数参数以及它们的类型
$ dwarfdump /usr/lib/debug/usr/bin/ls.debug | grep ce4
0xce4 [, 0] NS
$ addr2line -e /usr/lib/debug/usr/bin/ls.debug 0xce4
/usr/src/debug/coreutils-8./src/ls.c:
试一试dwarfdump来查看二进制文件里嵌入的信息。addr2line也能用到这些信息:
很多源代码层的调试命令会依赖于这些信息,比如next命令,这会在下一行的地址设置一个断点,那个print命令会依赖于变量的类型来输出(char、int、float,而不是二进制或十六进制)。
最后总结
我们已经见过调试器内部的好多方面了,所以我只会最后说几点:
这个堆栈跟踪信息也是通过当前的帧是向上“解开(unwinded)”的($sp和$bp/#fp),每个堆栈帧处理一次。函数的名称和参数以及本地变量名可以在调试信息中找到。
监视点(<code>watchpoints)是通过处理器的帮助(如果有)实现的:在寄存器里标记哪些地址应该被监控,然后它会在那内存被读写的时候引发一个异常。如果不支持这项功能,或者你请求的断点超过了处理器所支持的……那么调试器就会回到“手动”监视:一个指令一个指令地执行这个程序,并检查是否当前的操作到达了一个监视点的地址。是的,这很慢!
反向调试也可以这样进行,记录每个操作的效果,并反向执行。
条件断点是正常的断点,除非在内部,调试器在将控制权交给用户前检查当前的情况。如果当前的情况不满足,程序将会默默地继续运行。
还可以玩gdb gdb,或者更好的(好多了)gdb --pid $(pid of gdb),因为把两个调试器放到同一个终端里是疯狂的:-)。还可以调试系统:
qemu-system-i -gdb tcp::
gdb --pid $(pidof qemu-system-i)
gdb /boot/vmlinuz --exec "target remote localhost:"
C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind Linux 下 Valgrind 工具的全面使用指南
Valgrind 是一个多功能工具,用于内存调试、内存泄漏检测和性能分析,其发音为 [wɑːɡrɪnd]。本指南将详细介绍如何在 Linux 系统上安装 Valgrind,以及如何在不同操作系统上进行交叉编译。以下是在 Ubuntu 或其他 Debian 系统上的安装步骤:
对于其他 Linux 发行版如 Fedora 或 CentOS,可以使用相应的包管理器(例如 yum 或 dnf)来安装 Valgrind。例如,在 Fedora 上的安装命令如下:
在 macOS 上,可使用 Homebrew 来安装 Valgrind。
请注意,Valgrind 在最新版本的 macOS 上可能不支持。
在 Windows 系统上,由于 Valgrind 直接不可用,可以使用 Windows 的子系统 Linux(WSL)来运行它。
Valgrind 的源代码包含了所有运行所需库,通常无需额外依赖。然而,编译 Valgrind 需要基本的开发工具,包括 C 编译器(如 gcc)和 make 工具。若计划在不同平台上交叉编译 Valgrind,需配置交叉编译器,并确保编译环境包含所有 Valgrind 所需的头文件和库。
交叉编译的基本步骤如下:
请注意,步骤仅供参考,具体调整需根据实际需求和交叉编译环境。
Valgrind 是一个强大的工具,专用于内存管理错误检测、CPU 和内存分析。其使用方法包括但不限于常规检测、内存泄漏检测和性能分析。常规检测通常在程序结束后生成报告,而 Memcheck 内存泄漏检测工具可精确到源代码行,告知未正确释放的内存分配位置。使用 Memcheck 需确保程序和动态库包含调试信息,如使用 gcc 的-g 选项编译。
报告输出至文件功能允许将内存泄漏和其他问题的报告从控制台重定向到文件,以便于后续分析。重要参数如 --leak-check=full 可提供更详细的输出。
Valgrind 适用于长时间运行的服务,通过 gdbserver 模式在运行时与工具交互。同时,报告输出至文件功能有助于分析长时间运行程序的内存使用情况。
对于长时间运行程序的分析,使用 Massif 堆栈检测工具可以发现程序在运行过程中的内存使用问题。通过运行 Massif 并使用 ms_print 命令查看报告,可以找出内存使用异常或持续增长的情况。
Callgrind 性能分析工具用于收集程序的运行时行为信息,如函数调用次数和指令读取次数。虽然这些信息可以提供性能洞察,但它们不直接反映函数执行时间。要深入了解函数执行时间,可能需要结合其他性能分析工具或技术。
Valgrind 的不同工具(如 Memcheck、Callgrind、Massif 等)不能同时运行。每次运行 Valgrind 时,必须选择一个要使用的工具,以确保工具特定的目标和方法得到正确应用。
使用 Valgrind 进行分析时,应避免使用 kill -9 杀死进程,因为这可能影响检测结果。应尽量使用 kill 命令(不带 -9 选项)发送 TERM 信号,以优雅地终止进程。同时,tool 工具无法同时使用,每次运行 Valgrind 需明确选择一个工具。
VScode配置MASM运行环境(断点/运行/debug/配合emu(非DOSBox))
一、之所以不用DOSBox
不利于集成到VScode,执行代码需要新开窗口,配置繁琐。ps:而且加断点和调试复杂。
感谢 masm-code插件的作者,终于可以补全啦qaq
添加关于调试的一些建议
二、步骤
0.网络状况不好可以用度盘资源:
// update
dos-player
masm
1.安装VScode
2.安装masm-code, code runner插件
3.从 masm sdk ver下载所需的masm,如果下载不了可以自行换 镜像,或者百度下载。
3.1.解压,然后换路径,一路yes就可以了。
3.2.记下安装位置,比如我的在g盘根目录。
4.百度或谷歌搜索 ms-dos player,下载这个软件,或者从链接的GitHub上面下载源码,编译安装。
4.1.解压里面的msdos到X:\Program Files (x),X为想安装到的盘符。
4.2.记下安装位置。
5.添加环境变量:对于win,win+R输入SystemPropertiesAdvanced>环境变量>系统变量>找到Path变量>打开>添加如下两个变量(根据安装masm和msdos的位置不同前面的路径要调整):
*note:如果不是位系统就在binary文件夹下选择相应的文件夹
*note:可以测试,如果加入Path成功,在PowerShell里面输入msdos , ml /? 以及link /?会有出现相应的信息。如果没有可以尝试重启。
6.打开VScode, ctrl+shift+P打开搜索json,选择:
7.在末尾添加设置:
*经评论中@影生 提出,将选项/AT去除然后将输出文件的扩展名改为.EXE,这样可以用伪指令格式书写程序。
三、helloworld测试
这里的.code 写法相当于 :
详细如下:
输出:
一键运行。
// update
利用emu完成复杂的调试:
**先安装open插件
**在emu设置中设置(工具栏)默认打开(右下角)
emu for win
在vsc里面编辑好文件后利用ctrl+alt+O用emu打开,然后进行各种调试
note:如果要链接额外的文件可以参考官方文档:
ML and ML Command-Line Referencenote:
MSVC Linker options:
*note:链接器的选项要写在/link选项
三、关于调试
最近又找到了一个调试汇编的方案,如果成功的话可以用vscode的native debug来调试:
路线1:编译安装djgpp版本的gdb -- 安装到dos里面 --利用里面的gdbserver连接虚拟机的COM端口 -- vsocde里面用native debug 连接,调试
路线2(最接近完成):利用上面dosbox-x 的patch --编译dosbox-x --连接gdbserver -- vscode 用native debug连接
路线3:利用wsl或者msys2安装或者编译qemu -- 利用gdbserver连接COM口 -- 连接vscode
但是现在的难点是找不到相应dosbox-x的版本,如果有找到的大佬麻烦留言呀!
后续应该会怎加一些汇编的例子和专门讲比较c和汇编的文章。()