Linux x86_64 C语言实现gdb断点机制

文章目录

  • 前言
  • 一、trap指令简介
  • 二、调用ptrace
  • 三、创建breakpoints
  • 四、CONT 和 SINGLESTEP
  • 五、完整代码演示
  • 六、增加参数检测
  • 参考资料

前言

本文参考文章:Implementing breakpoints on x86 Linux

一、trap指令简介

将通过在断点地址向目标进程的内存中插入一条新的CPU指令来实现断点。此指令应暂停目标进程的执行,并将控制权交还给操作系统,或者说将目标进程的控制权转移给其他进程,通过是调试进程。

有很多方法可以将控制权返回到操作系统,但希望最大限度地减少对正在进行热修补的代码的干扰。x86提供的int3指令,编码为单字节0xCC:在这里插入图片描述

当CPU执行int3时,它将停止它正在做的事情,并跳到内核函数do_int3函数服务例程,这是操作系统内核中的一段代码。在Linux上,此例程将向当前进程(即目标进程)发送信号SIGTRAP。

备注:除了int3将向当前进程发送信号SIGTRAP信号外,调试器给目标进程发送PTRACE_SYSCALL和PTRACE_SINGLESTEP这个两个ptrace请求时,目标进程看起来也可以看作接收到了一个SIGTRAP信号而停止执行。调试器可以在目标进程停止时进行进一步的检查或操作。
因此调试器可以在三种情况下检查目标进程:

断点  -- int3 
单步执行指令 -- PTRACE_SINGLESTEP
系统调用 -- PTRACE_SYSCALL

前两者用于调试器,比如gdb,后者用于strace。

由于我们将int3放入目标的代码中,因此目标将收到一个SIGTRAP。在正常情况下,这将调用目标的SIGTRAP处理程序,该处理程序通常会杀死进程。相反,我们希望跟踪过程拦截该信号,并将其解释为目标击中断点。我们将通过ptrace系统调用来实现这一点。

关于int3指令可以参考:GDB 源码分析 – 断点源码解析

定义trap指令:

#include <sys/reg.h>#define REGISTER_IP RIP
#define TRAP_LEN    1
#define TRAP_INST   0xCC
#define TRAP_MASK   0xFFFFFFFFFFFFFF00

常量RIP定义在sys/reg.h文件中,用于标识保存指令指针的机器寄存器。

#ifdef __x86_64__
/* Index into an array of 8 byte longs returned from ptrace forlocation of the users' stored general purpose registers.  */
......
# define RIP	16
......

trap instruction存储为整数TRAP_INST,其字节长度为TRAP_LEN。这些在32位和64位x86上是相同的。陷阱指令是一个单字节,但我们将以一个机器字为增量读取和写入目标的内存,即32或64位。因此,我们将读取4或8个字节的机器代码,用TRAP_MASK清除第一个字节,并替换0xCC。由于x86是一个小端序体系结构,内存中的第一个字节是整数机器字的最低有效字节。

二、调用ptrace

所有各种ptrace请求都是通过一个名为ptrace的系统调用发出的。第一个参数指定请求的类型,第二个参数几乎总是目标的进程ID。

NAMEptrace - process traceSYNOPSIS#include <sys/ptrace.h>long ptrace(enum __ptrace_request request, pid_t pid,void *addr, void *data);

ptrace是Linux操作系统提供的一个系统调用,用于实现进程间的跟踪和调试功能。通过ptrace系统调用,一个进程(称为追踪器)可以监视和控制另一个进程(称为被追踪进程)的执行。
以下是ptrace系统调用的一些常见用法和功能:

(1)进程跟踪:追踪器可以使用ptrace系统调用启动对一个进程的追踪。追踪器可以监视被追踪进程的系统调用、信号传递、执行状态等,并在需要时对其进行控制。

(2)单步执行:通过使用ptrace系统调用的PTRACE_SINGLESTEP选项,追踪器可以实现单步执行功能,逐条执行被追踪进程的指令并进行调试和分析。

(3)寄存器访问:追踪器可以使用ptrace系统调用的PTRACE_GETREGS和PTRACE_SETREGS选项来读取和修改被追踪进程的寄存器状态,以实现寄存器级别的调试和修改。

(4)内存访问:通过ptrace系统调用的PTRACE_PEEKDATA和PTRACE_POKEDATA选项,追踪器可以读取和写入被追踪进程的内存数据,以进行内存级别的调试和修改。

(5)信号控制:追踪器可以使用ptrace系统调用的PTRACE_GETSIGINFO和PTRACE_SETSIGINFO选项来获取和修改被追踪进程收到的信号信息,以实现对信号的控制和处理。

(6)进程控制:通过ptrace系统调用的PTRACE_ATTACH和PTRACE_DETACH选项,追踪器可以附加到一个正在运行的进程并开始追踪,或者从被追踪进程中分离出来。

在我们可以调式目标进程之前,我们需要附加到它:

void breakfast_attach(pid_t pid) {int status;ptrace(PTRACE_ATTACH, pid);//调用waitpid等待子进程停止的通知waitpid(pid, &status, 0);//使用ptrace系统调用和PTRACE_SETOPTIONS选项来设置追踪器的选项,可以获取子进程的退出码和信号信息。//PTRACE_SETOPTIONS用于设置追踪器的选项//pid是要追踪的子进程的进程ID//PTRACE_O_TRACEEXIT是用于追踪子进程退出的特殊选项。ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXIT);}

ptrace(PTRACE_ATTACH, pid) 来使指定进程号为pid的进程进入被追踪模式,这是一种使进程号为pid的进程被动进入被追踪模式。

PTRACE_ATTACH请求将使用SIGSTOP停止目标进程。我们等待目标进程接收到这个信号。

PTRACE_ATTACHAttach  to  the process specified in pid, making it a tracee of the calling process.  The tracee is sent a SIGSTOP, but will not necessarily have stopped by the com‐pletion of this call; use waitpid(2) to wait for the tracee to stop.

附加到在PID中指定的进程,使其成为调用进程的跟踪对象(tracee)。调用进程 tracer 给 tracee发送一个SIGSTOP信号,但不一定会在此调用完成时停止;使用waitid等待tracee停止。

请注意,ptrace和waitpid可能会以各种方式失败。在实际应用程序中,需要检查返回值和/或errno。为了简洁起见,在本文中省略了这些检查。

使用另一个ptrace请求来获取目标的指令指针的值:

target_addr_t breakfast_getip(pid_t pid) {long v = ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*REGISTER_IP);//恢复执行时,将返回并执行用陷阱覆盖的原始指令。减去TRAP_LEN,得到下一条指令的真实地址return (target_addr_t) (v - TRAP_LEN);
}
PTRACE_PEEKUSERRead a word at offset addr in the tracee's USER area, which holds the registers and other information about the process (see <sys/user.h>).  The word is returned  asthe result of the ptrace() call.

读取Tracee用户区中偏移量addr处的一个word ,该用户区保存寄存器和有关该过程的其他信息(参见<sys/user.h>)。该word 将作为ptrace()调用的结果返回。

由于目标进程已挂起,因此它不会在任何CPU上运行,并且它的指令指针也不会存储在实际的CPU寄存器中。相反,它被保存到内核内存中的“用户区域”。我们使用PTRACE_PEEKUSER请求以指定的字节偏移量从该区域读取机器字。sys/regs.h中的常量给出了寄存器的出现顺序,因此我们只需乘以sizeof(long)。
x86_64平台下一个寄存器八个字节。

#ifdef __x86_64__
/* Index into an array of 8 byte longs returned from ptrace forlocation of the users' stored general purpose registers.  */# define R15	0
# define R14	1
# define R13	2
# define R12	3
# define RBP	4
# define RBX	5
# define R11	6
# define R10	7
# define R9	8
# define R8	9
# define RAX	10
# define RCX	11
# define RDX	12
# define RSI	13
# define RDI	14
# define ORIG_RAX 15
# define RIP	16
# define CS	17
# define EFLAGS	18
# define RSP	19
# define SS	20
# define FS_BASE 21
# define GS_BASE 22
# define DS	23
# define ES	24
# define FS	25
# define GS	26

在我们遇到断点后,保存的IP指向陷阱指令之后的指令。当我们恢复执行时,我们将返回并执行用陷阱覆盖的原始指令。所以我们减去TRAP_LEN,得到下一条指令的真实地址。

三、创建breakpoints

关于断点,我们需要记住两件事:我们替换的代码的地址和最初存在于那里的原始代码。

struct breakpoint {target_addr_t addr;   //替换的代码的地址long orig_code;		//原始代码指令
};

要启用断点,我们保存原始代码并插入陷阱指令:

static void enable(pid_t pid, struct breakpoint *bp) {//read bp->addr -->获取原始指令long orig = ptrace(PTRACE_PEEKTEXT, pid, bp->addr);//write 0xCC into bp->addr -->插入陷阱指令:0xCCptrace(PTRACE_POKETEXT, pid, bp->addr, (orig & TRAP_MASK) | TRAP_INST);//保存原始指令bp->orig_code = orig;
}

PTRACE_PEEKTEXT请求从目标的代码地址空间读取一个机器字,由于历史原因,该地址空间被命名为“text”。PTRACE_POKETEXT写入该空间。在x86 Linux上,代码空间和数据空间实际上没有区别,因此PTRACE_PEEKDATA和PTRACE_POKEDATA也可以正常工作。

PTRACE_PEEKTEXT, PTRACE_PEEKDATARead  a  word  at the address addr in the tracee's memory, returning the word as the result of the ptrace() call.  Linux does not have separate text and data addressspaces, so these two requests are currently equivalent. 
PTRACE_POKETEXT, PTRACE_POKEDATACopy the word data to the address addr in the tracee's memory.  As for PTRACE_PEEKTEXT and PTRACE_PEEKDATA, these two requests are currently equivalent.

创建断点非常简单:

struct breakpoint *breakfast_break(pid_t pid, target_addr_t addr) {struct breakpoint *bp = malloc(sizeof(*bp));bp->addr = addr;//启用断点 --> 插入陷阱指令:0xCCenable(pid, bp);return bp;
}

要禁用断点,我们只需写回保存的word(原始指令):

//写回保存的原始指令
static void disable(pid_t pid, struct breakpoint *bp) {ptrace(PTRACE_POKETEXT, pid, bp->addr, bp->orig_code);
}

四、CONT 和 SINGLESTEP

一旦我们连接到目标,它的执行就会停止。以下是如何恢复它:

static int run(pid_t pid, int cmd) {int status, last_sig = 0, event;while (1) {ptrace(cmd, pid, 0, last_sig);waitpid(pid, &status, 0);if (WIFEXITED(status))return 0;if (WIFSTOPPED(status)) {last_sig = WSTOPSIG(status);if (last_sig == SIGTRAP) {event = (status >> 16) & 0xffff;return (event == PTRACE_EVENT_EXIT) ? 0 : 1;}}}
}int breakfast_run(pid_t pid, struct breakpoint *bp) {if (bp) {ptrace(PTRACE_POKEUSER, pid, sizeof(long)*REGISTER_IP, bp->addr);//恢复原始指令disable(pid, bp);//单步执行原始指令//父进程通过PTRACE_SINGLESTEP以及子进程的id号来调用ptrace。//这么做是告诉操作系统——请重新启动子进程,但当子进程执行了下一条指令后再将其停止。if (!run(pid, PTRACE_SINGLESTEP))return 0;//重新启用断点enable(pid, bp);}return run(pid, PTRACE_CONT);
}

我们要求ptrace继续执行——但如果我们从断点恢复,我们必须首先进行一些清理。我们回退指令指针,以便下一条要执行的指令在断点处。然后我们禁用断点,使目标只执行一条指令,单步执行断点处的原始指令。一旦我们通过了断点,我们就可以在下次重新启用它。如果目标退出,run将返回0,理论上这可能发生在我们的单个步骤中。

断点处理过程:

命中断点-->触发int3异常-->调试器观测目标进程-->调试完毕后,恢复原始指令(回退指令指针,回退一个字节)-->单步执行原始指令-->重新下断点0xcc-->目标进程继续运行

对于gdb调试器:
当断点命中中断到调试器时,调试器会把所在断点处的 int 3指令恢复成原始指令。因此,在用户发出了恢复执行命令后,此时断点处的指令已经是正常的原始指令了,因此要做一些处理,以至于下次还能继续命中该断点。调试器在通知系统真正恢复程序执行前,调试器需要将断点列表中的该断点位置重新启用该断点。但是对于刚才命中的这个断点需要特别对待,试想如果把这个断点处的指令也替换为int 3指令,那么程序一执行便又触发断点了。但是如果不替换,那么这个断点便没有被启动,程序下次执行到这里时就不会触发断点,而用户并不知道这一点。对于这个问题,大多数调试器的做法都是先单步执行一次,单步执行一条指令。也就是说,先设置单步执行标志,然后恢复执行,将断点所在位置的指令执行完。因为设置了单步标志,所以,CPU执行完断点位置的这条指令后会立刻再中断到调试器中,这一次调试器不会通知用户,会做一些内部操作后便立刻恢复程序执行,而且将该断点启动。

PTRACE_CONTRestart the stopped tracee process.  If data is nonzero, it is interpreted as the number of a signal to be delivered to the tracee; otherwise, no  signal  is  deliv‐ered.  Thus, for example, the tracer can control whether a signal sent to the tracee is delivered or not.

PTRACE_CONT是一个用于重新启动被停止的被追踪进程的ptrace系统调用选项。当tracer调用PTRACE_CONT时,被追踪的进程tracee将继续执行。
PTRACE_CONT的行为如下:

如果提供的data参数为非零值,则被解释为要发送给被追踪进程的信号编号。这意味着可以控制是否向被追踪进程发送信号。
如果data参数为零,则不向被追踪进程发送任何信号。
当调用PTRACE_CONT时,被追踪进程将从之前被停止的位置继续执行,并且可能会在之后再次被停止,具体取决于陷阱事件和追踪器的设置。
追踪器可以通过调用ptrace系统调用并使用PTRACE_CONT选项来控制被追踪进程的执行流程,包括在适当的时机发送信号以及决定是否重新启动进程。

PTRACE_CONT是一种ptrace系统调用选项,用于重新启动被停止的被追踪进程,并可选择发送信号给被追踪进程。通过使用PTRACE_CONT,追踪器可以对被追踪进程的执行进行控制和管理。

对于run函数:

static int run(pid_t pid, int cmd) {int status, last_sig = 0, event;while (1) {ptrace(cmd, pid, 0, last_sig);//父进程会调用waitpid来等待子进程的退出/停止,以便获取子进程的退出/停止状态,并进行相应的处理。//父进程通过waitpid正在等待子进程退出/停止这个事件发生。//当被调试进程被内核挂起时-- 停止,内核会向其父进程发送一个 SIGCHLD 信号,父进程可以通过调用 waitpid() 系统调用来捕获这个信息waitpid(pid, &status, 0);//当子进程退出/停止时,父进程通过 waitpid 来获取子进程的退出状态:status //退出 -- 使用WIFEXITED宏来判断子进程是否正常退出//在正常运行这个跟踪程序时,会得到子进程正常退出(WIFEXITED会返回true)的信号。if (WIFEXITED(status))return 0;//增加一次额外的检查//停止 -- WIFSTOPPED宏定用于在处理子进程状态时判断子进程是否处于停止状态。//一旦子进程停止(如果子进程由于发送的信号而停止运行,WIFSTOPPED就返回true), 父进程就去检查这个事件if (WIFSTOPPED(status)) {//通过相关宏 WSTOPSIG 检查子进程停止运行的信号//WSTOPSIG宏定义用于从子进程的状态值中提取导致子进程停止的信号编号last_sig = WSTOPSIG(status);//在SIGTRAP的情况下,我们检查状态的位16-31的值PTRACE_EVENT_EXIT,它指示目标即将退出if (last_sig == SIGTRAP) {event = (status >> 16) & 0xffff;//如果状态的位16-31的值PTRACE_EVENT_EXIT,表明目标进程即将退出,也要返回0return (event == PTRACE_EVENT_EXIT) ? 0 : 1;}}}
}

cmd是PTRACE_CONT或PTRACE_SINGLESTEP。对于PTRACE_SINGLESTEP,OS将设置一个控制位,以使CPU在一条指令完成后引发int3,即单步调试功能。

PTRACE_SYSCALL, PTRACE_SINGLESTEPRestart the stopped tracee as for PTRACE_CONT, but arrange for the tracee to be stopped at the next entry to or exit from a system call, or after execution of a sin‐gle instruction, respectively.  (The tracee will also, as usual, be stopped upon receipt of a signal.)  From the tracer's perspective, the tracee will appear to havebeen  stopped  by  receipt  of  a  SIGTRAP.   So, for PTRACE_SYSCALL, for example, the idea is to inspect the arguments to the system call at the first stop, then doanother PTRACE_SYSCALL and inspect the return value of the system call at the second stop.  The data argument is treated as for PTRACE_CONT.

用于单步执行被追踪进程的指令。当使用PTRACE_SINGLESTEP选项调用ptrace时,被追踪进程会在执行一条指令后停止。
以下是关于PTRACE_SINGLESTEP的一些要点:

当被追踪进程收到PTRACE_SINGLESTEP指令后,它会执行一条指令,并在执行完毕后立即停止。这样,追踪器就有机会检查指令的执行结果、寄存器状态或其他相关信息。
追踪器可以利用这个停止点来实现单步调试的功能,例如在每个步骤中检查变量的值、跟踪指令执行路径或进行其他调试操作。

在SIGTRAP的情况下,我们检查停止状态位16-31的值PTRACE_EVENT_EXIT,它指示目标进程即将退出。回想一下,我们通过设置选项PTRACE_O_TRACEEXIT请求了此通知。你可能会认为(至少,我是这么认为的)检查WIFEXITED就足够了。但我遇到了一个问题,向目标发送致命信号会使跟踪过程永远循环。我通过增加一次额外的检查来解决这个问题。
如果启用了 PTRACE_O_TRACEEXIT 选项,会在实际终止之前发生 PTRACE_EVENT_EXIT 事件。

五、完整代码演示

// breakfast.h#ifndef _BREAKFAST_H
#define _BREAKFAST_H#include <sys/types.h>  /* for pid_t */typedef void *target_addr_t;
struct breakpoint;void breakfast_attach(pid_t pid);
target_addr_t breakfast_getip(pid_t pid);
struct breakpoint *breakfast_break(pid_t pid, target_addr_t addr);
int breakfast_run(pid_t pid, struct breakpoint *bp);#endif
// breakfast.c#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <stdlib.h>#include "breakfast.h"#define REGISTER_IP RIP
#define TRAP_LEN    1
#define TRAP_INST   0xCC
#define TRAP_MASK   0xFFFFFFFFFFFFFF00void breakfast_attach(pid_t pid) {int status;ptrace(PTRACE_ATTACH, pid);waitpid(pid, &status, 0);ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXIT);
}target_addr_t breakfast_getip(pid_t pid) {long v = ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*REGISTER_IP);return (target_addr_t) (v - TRAP_LEN);
}struct breakpoint {target_addr_t addr;long orig_code;
};static void enable(pid_t pid, struct breakpoint *bp) {long orig = ptrace(PTRACE_PEEKTEXT, pid, bp->addr);ptrace(PTRACE_POKETEXT, pid, bp->addr, (orig & TRAP_MASK) | TRAP_INST);bp->orig_code = orig;
}static void disable(pid_t pid, struct breakpoint *bp) {ptrace(PTRACE_POKETEXT, pid, bp->addr, bp->orig_code);
}struct breakpoint *breakfast_break(pid_t pid, target_addr_t addr) {struct breakpoint *bp = malloc(sizeof(*bp));bp->addr = addr;enable(pid, bp);return bp;
}static int run(pid_t pid, int cmd) {int status, last_sig = 0, event;while (1) {ptrace(cmd, pid, 0, last_sig);waitpid(pid, &status, 0);if (WIFEXITED(status))return 0;if (WIFSTOPPED(status)) {last_sig = WSTOPSIG(status);if (last_sig == SIGTRAP) {event = (status >> 16) & 0xffff;return (event == PTRACE_EVENT_EXIT) ? 0 : 1;}}}
}int breakfast_run(pid_t pid, struct breakpoint *bp) {if (bp) {ptrace(PTRACE_POKEUSER, pid, sizeof(long)*REGISTER_IP, bp->addr);disable(pid, bp);if (!run(pid, PTRACE_SINGLESTEP))return 0;enable(pid, bp);}return run(pid, PTRACE_CONT);
}
// test.c#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/user.h>#include "breakfast.h"int fact(int n) {if (n <= 1)return 1;return n * fact(n-1);
}void child() {//getpid()获取子进程的pid,给其发送 SIGSTOP 信号kill(getpid(), SIGSTOP);printf("fact(5) = %d\n", fact(5));
}void parent(pid_t pid) {struct breakpoint *fact_break, *last_break = NULL;void *fact_ip = fact, *last_ip;//该pid是子进程的pidbreakfast_attach(pid);fact_break = breakfast_break(pid, fact_ip);while (breakfast_run(pid, last_break)) {last_ip = breakfast_getip(pid);if (last_ip == fact_ip) {int arg = ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*RDI);printf("Break at fact(%d)\n", arg);last_break = fact_break;} else {printf("Unknown trap at %p\n", last_ip);last_break = NULL;}}
}int main() 
{pid_t pid = fork();if(pid == 0)//子进程child();else if(pid > 0)//父进程,pid是子进程pidparent(pid);else{printf("fork error\n");return -1;}return 0;
}

调用fork函数,返回一个父进程和一个子进程。子进程会做一些希望观察到的计算。这里计算一下著名的阶乘函数:

int fact(int n) {if (n <= 1)return 1;return n * fact(n-1);
}void child() {//getpid()获取子进程的pid,给其发送 SIGSTOP 信号kill(getpid(), SIGSTOP);printf("fact(5) = %d\n", fact(5));
}

父进程将调用PTRACE_ATTACH 并发送子进程SIGSTOP,但子进程可能会在父进程有机会调用ptrace之前完成执行。所以让孩子自己停止自己。在附加到长时间运行的进程时,这不是问题。

实际上,对于fork-跟踪子模式,应该使用PTRACE_TRACEME。
PTRACE_TRACEME – 被调试的进程调用 ptrace(PTRACE_TRACEME, …) 来使自己进入被追踪模式,是进程自己主动进入被追踪模式。gdb调试程序时便是采用此种模式。
我们这里只是做一个小的实验,选择用了PTRACE_ATTACH模式。

父进程用breakfast_break给子进程设置断点:

void parent(pid_t pid) {struct breakpoint *fact_break, *last_break = NULL;void *fact_ip = fact, *last_ip;breakfast_attach(pid);fact_break = breakfast_break(pid, fact_ip);while (breakfast_run(pid, last_break)) {last_ip = breakfast_getip(pid);if (last_ip == fact_ip) {printf("Break at fact()\n");last_break = fact_break;} else {printf("Unknown trap at %p\n", last_ip);last_break = NULL;}}
}

原则上,我们可以使用breakfast 来跟踪我们拥有的任何正在运行的进程,即使我们没有它的源代码。但我们仍然需要一种方法来找到有趣的断点地址。在这里,这是我们想要的最简单的方法:fork()通过一个进程(父进程)创建一个新进程(子进程),子进程是父进程的副本,因此子进程和父进程共享代码段,所以父子进程 fact function 地 址的一样。

# ./test
Break at fact()
Break at fact()
Break at fact()
Break at fact()
Break at fact()
fact(5) = 120

六、增加参数检测

计数函数调用的功能对于性能评测已经很有用了。但我们通常希望从停止的目标中获得更多信息。让我们看看我们是否能读懂传递给fact函数的参数。这部分将专门针对64位x86,尽管这个想法是通用的。

每个体系结构都定义了一个C调用约定,该约定指定了函数参数的传递方式,通常使用寄存器和堆栈槽的组合。在64位x86上,第一个参数在RDI寄存器中传递。可以通过运行objdump-d测试并查看反汇编的代码来验证这一点:
在这里插入图片描述
由于fact函数的参数类型时int,因此用RDI寄存器的低32位即可,即EDI寄存器。

因此,我们将修改test.c以读取此寄存器:

void parent(pid_t pid) {struct breakpoint *fact_break, *last_break = NULL;void *fact_ip = fact, *last_ip;//该pid是子进程的pidbreakfast_attach(pid);//设置断点fact_break = breakfast_break(pid, fact_ip);//breakfast_run函数中当执行断点原始指令后会重新启用断点//断点的流程:int3 --> 恢复原始指令 --> 单步执行原始指令 -->重新启用断点while (breakfast_run(pid, last_break)) {//子进程此时是stopped状态,指令指针寄存器的值保存到内核内存中的“用户区域”//来获取子进程指令指针的值last_ip = breakfast_getip(pid);if (last_ip == fact_ip) {//读取寄存器RDI的值 -- 函数调用时RDI用来传递第一个参数int arg = ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*RDI);printf("Break at fact(%d)\n", arg);last_break = fact_break;} else {printf("Unknown trap at %p\n", last_ip);last_break = NULL;}}
}
# ./test
Break at fact(5)
Break at fact(4)
Break at fact(3)
Break at fact(2)
Break at fact(1)
fact(5) = 120

x86体系结构具有用于设置断点的专用寄存器,但受到各种限制。我们忽略了这个特性,而选择了更灵活的软件断点。硬件断点可以做一些这种技术做不到的事情,比如中断从特定内存地址读取的操作。

参考资料

Implementing breakpoints on x86 Linux
https://blog.csdn.net/dog250/article/details/106267041
https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/65172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试中的商业思维:如何展示你对业务的理解

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

SourceTree 使用技巧

参考资料 SourceTree使用教程&#xff08;一&#xff09;—克隆、提交、推送SourceTree的软合并、混合合并、强合并区别SourceTree 合并分支上的多个提交&#xff0c;一次性合并分支的多次提交至另一分支&#xff0c;主分支前进时的合并冲突解决 目录 一. 基础设置1.1 用户信息…

VR司法法治教育平台,沉浸式课堂教学培养刑侦思维和能力

VR司法法治教育平台提供了多种沉浸式体验&#xff0c;通过虚拟现实(Virtual Reality&#xff0c;简称VR)技术让用户深度参与和体验法治知识。以下是一些常见的沉浸式体验&#xff1a; 1.罪案重现 VR司法法治教育平台可以通过重现真实案例的方式&#xff0c;让用户亲眼目睹罪案发…

一文了解气象站是什么,作用有哪些?

气象站被广泛应用于气象、农业、交通、能源、水利、环保等领域&#xff0c;是一种用于收集、分析和处理气象数据的设备&#xff0c;能够为人们提供及时、准确的气象数据和决策支持。 气象站一般由传感器、环境监控主机和监控平台组成。传感器能够测量各种气象要素&#xff0c;…

Spring 中存取 Bean 的相关注解

目录 一、五大类注解 1、五大类注解存储Bean对象 1.1Controller(控制器储存) 1.2Service(服务存储) 1.3Repository(仓库存储) 1.4Component(组件存储) 1.5Configuration(配置存储) 2、五大类注解小结 2.1为什么要这么多类注解 2.2 五大类注解之间的关系 二、方法注解 1.方法注…

SQL-子查询

SQL 子查询 是指将一个SELECT查询&#xff08;子查询&#xff09;的结果用括号括起来作为另一个SQL语句的数据来源或者判断条件

【Bug】Ubuntu 有线设置打不开无反应

前言&#xff1a; 突然有线设置就没法启用了&#xff0c;但是能联网&#xff0c;能查看ip 解决&#xff1a; 最后安装了一个新的依赖包&#xff1a; sudo apt install gnome-control-center 然后就可以了 还有一个方法&#xff0c;没试过&#xff0c;但感觉有点道理的&#…

在抖音中使用语聚AI,实现自动回复用户视频评论、私信问答

您可以通过集简云数据流程&#xff0c;将语聚AI助手集成到抖音视频评论、抖音私信&#xff0c;实现自动回复用户视频评论、私信问答&#xff0c;大大提升账号互动与运营效率。 效果如下&#xff1a; 自动化流程&#xff1a; ● 抖音普通号评论对接语聚AI&#xff08;点击可一…

宏观经济和风电预测误差分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

pytorch异常——RuntimeError:Given groups=1, weight of size..., expected of...

文章目录 省流异常报错异常截图异常代码原因解释修正代码执行结果 省流 nn.Conv2d 需要的输入张量格式为 (batch_size, channels, height, width)&#xff0c;但您的示例输入张量 x 是 (batch_size, height, width, channels)。因此&#xff0c;需要对输入张量进行转置。 注意…

LLM学习笔记(1)

学习链接 ChatGPT Prompt Engineering for Developers - DeepLearning.AI 一、prompt engineering for developer 1、原则 prompting principles and iterative pattern 2、用于summarize 环境与helper functions import openai import osfrom dotenv import load_dotenv…

[C++] STL_list常用接口的模拟实现

文章目录 1、list的介绍与使用1.1 list的介绍1.2 list的使用 2、list迭代器3、list的构造4、list常用接口的实现4.1 list capacity4.2 插入删除、交换、清理4.2.1 insert任意位置插入4.2.2 push_front头插4.2.3 push_back尾插4.2.4 erase任意位置删除4.2.5 pop_front头删4.2.6 …

Redis之管道解读

目录 基本介绍 使用例子 管道对比 管道与原生批量命令对比 管道与事务对比 使用pipeline注意事项 基准测试 基本介绍 Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务器。 这意味着请求通常按如下步骤处理&#xff1a; 客户端发送一个请求到服务器&am…

java 八股文 基础 每天笔记随机刷

Component 和 PostConstruct 搭配使用 被Component注解标识的类在应用程序启动时会被实例化&#xff0c;并由Spring容器进行管理。PostConstruct是一个Java注解&#xff0c;用于标记一个方法在类被实例化后自动执行。该方法必须是非静态的&#xff0c;没有参数&#xff0c;且不…

数字货币量化交易平台

数字货币量化交易平台是近年来金融科技领域迅速崛起的一种创新型交易方式。它通过应用数学模型和算法策略&#xff0c;实现对数字货币市场的自动交易和风险控制。然而&#xff0c;要在这个竞争激烈的领域中脱颖而出&#xff0c;一个数字货币量化交易平台需要具备足够的专业性&a…

正中优配:A股早盘三大股指微涨 华为概念表现活跃

周三&#xff08;8月30日&#xff09;&#xff0c;到上午收盘&#xff0c;三大股指团体收涨。其间上证指数涨0.06%&#xff0c;报3137.72点&#xff1b;深证成指和创业板指别离涨0.33%、0.12%&#xff1b;沪深两市合计成交额6423.91亿元&#xff0c;总体来看&#xff0c;两市个…

java-数组

数组静态初始化写法&#xff1a; //静态初始化数组 int[] age new int[] {7,18,19}; double[] scores new double[]{67.5,77.8,94.2,99};//静态初始化数组简化写法 int[] age1 {7,18,19}; double[] scores2 {67.5,77.8,94.2,99};数组在内存中定义方式&#xff1a; 1.在内…

opencv的haarcascade_frontalface_default.xml等文件

文章目录 GitHub下载在安装好的OpenCV文件夹下寻找opencv-python中获取 GitHub下载 下载地址&#xff1a;https://github.com/opencv/opencv/tree/master/data/haarcascades 在安装好的OpenCV文件夹下寻找 路径如下&#xff1a; 你安装的opencv路径\OpenCV\opencv\build\et…

ELK安装、部署、调试(一)设计规划及准备

一、整体规划如图&#xff1a; 【filebeat】 需要收集日志的服务器&#xff0c;安装filebeat软件&#xff0c;用于收集日志。logstash也可以收集日志&#xff0c;但是占用的系统资源过大&#xff0c;所以使用了filebeat来收集日志。 【kafka】 接收filebeat的日志&#xff…

Can‘t connect to local MySQL server through socket ‘/tmp/mysql.sock‘

最近在用django框架开发后端时&#xff0c;在运行 $python manage.py makemigrations 命令时&#xff0c;报了以上错误&#xff0c;错误显示连接mysql数据库失败&#xff0c;查看了mysql数据库初始化配置文件my.cnf&#xff0c;我的mysql.sock文件存放路径配置在了/usr/local…