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.rhkb.cn/news/112456.html

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

相关文章

图像二值化

目录 1.固定值二值化 2.自适应阈值二值化 3.Android JNI完整代码 1.固定值二值化 固定阈值二值化是OpenCV中一种简单而常用的图像处理技术&#xff0c;用于将图像转换为二值图像。在固定阈值二值化中&#xff0c;像素值根据一个预定义的阈值进行分类&#xff0c;大于阈值的…

c语言实现二叉树(链式结构)

文章目录 前言一、二叉树的遍历1、二叉树的层序遍历2、二叉树的前序遍历3、二叉树的中序遍历4、二叉树的后序遍历5、代码实现 二、二叉树的一些操作的实现1、求二叉树的结点个数2、求二叉树叶子结点个数3、求二叉树第k层结点个数4、求二叉树深度5、二叉树中查找值为x的结点6、二…

streamlit-高级功能

缓存 st.cache_data st.cache_resource 为应用程序添加会话状态 会话状态 会话状态应用到应用程序 会话状态和小部件关联 可序列化会话状态 注意事项和限制 命令行选项 应用程序菜单 菜单选项 开发者选项 streamlit配置 按钮行为和示例 连接到数据 数据框 形式 小部件语义 …

前端vue引入高德地图入门教程

距离上一篇关于前端项目中使用高德地图的文章已经将近5年之久&#xff0c; 这是我的第一篇关于高德地图的文章 这期间前端技术日新月异&#xff0c;5年前JQuery还如日中天&#xff0c;如今已经销声匿迹&#xff0c;很少有公司招聘还在要求JQuery&#xff0c;更多的是Vue、React…

keras深度学习框架构建LeNet5神经网络模型实现手写数字识别

之前两篇文章分别通过keras深度学习框架构建简单神经网络和卷积神经网络实现过手写数字识别实验。这篇文章分享我根据LeNet5模型构建的卷积神经网络来实现手写数字识别。 这个实验是根据LeNet5模型构建卷积神经网络&#xff0c;LeNet5模型的原理图如下所示&#xff1a; 相信大家…

中欧财富:分布式数据库的应用历程和 TiDB 7.1 新特性探索

作者&#xff1a;张政俊 中欧财富数据库负责人 中欧财富是中欧基金控股的销售子公司&#xff0c;旗下 APP 实现业内基金品种全覆盖&#xff0c;提供基金交易、大数据选基、智慧定投、理财师咨询等投资工具及服务。中欧财富致力为投资者及合作伙伴提供一站式互联网财富管理解决方…

js优雅的统计字符串字符出现次数

题目如下 统计一串字符串中每个字符出现的频率 示例字符串 let str asdfasqwerqwrdfafafasdfopasdfopckpasdfassfd小白写法 let str asdfasqwerqwrdfafafasdfopasdfopckpasdfassfdlet result {}; for (let i 0; i < str.length; i) {if (result[str[i]]) {result[str[…

论文笔记: One Fits All:Power General Time Series Analysis by Pretrained LM

1 intro 时间序列领域预训练模型/foundation 模型的研究还不是很多 主要挑战是缺乏大量的数据来训练用于时间序列分析的基础模型——>论文利用预训练的语言模型进行通用的时间序列分析 为各种时间序列任务提供了一个统一的框架 论文还调查了为什么从语言领域预训练的Transf…

【ag-grid-vue】column

网格中的每一列都使用列定义(ColDef)来定义。列根据在网格选项中指定的列定义的顺序在网格中定位。 列定义 下面的例子展示了一个定义了3列的简单网格: <template><ag-grid-vuestyle"height: 300px; width: 1000px"class"ag-theme-balham":colum…

get√接口自动化核心知识点浓缩,为面试加分

日常接触到的接口自动化从实际目标可以划分为两大类&#xff1a; 1、为模拟测试数据而开展的接口自动化 这种接口自动化大多是单次执行&#xff0c;目的很明确是为了功能测试创造测试数据&#xff0c;节约人工造数据的时间和人工成本&#xff0c;提高功能测试人员的测试效率。…

chain of thought (思维链, cot)

定义 思维链 (Chain-of-thought&#xff0c;CoT) 的概念是在 Google 的论文 "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models" 中被首次提出。思维链&#xff08;CoT&#xff09;是一种改进的提示策略&#xff0c;用于提高 LLM 在复杂推理…

【UE5】给模型指定面添加自定义材质

实现步骤 1. 首先我们向UE中导入一个简单的模型&#xff0c;可以看到目前该模型的材质插槽只有一个&#xff0c;当我们修改材质时会使得模型整体的材质全部改变&#xff0c;如果我们只想改变模型的某些面的材质就需要继续做后续操作。 2. 选择建模模式 3. 在模式工具栏中点击…

Linux学习之Ubuntu 20使用systemd管理OpenResty服务

sudo cat /etc/issue可以看到操作系统的版本是Ubuntu 20.04.4 LTS&#xff0c;sudo lsb_release -r可以看到版本是20.04&#xff0c;sudo uname -r可以看到内核版本是5.5.19&#xff0c;sudo make -v可以看到版本是GNU Make 4.2.1。 需要先参考我的博客《Linux学习之Ubuntu 2…

SpringBoot Mybatis 多数据源 MySQL+Oracle

一、背景 在SpringBoot Mybatis 项目中&#xff0c;需要连接 多个数据源&#xff0c;连接多个数据库&#xff0c;需要连接一个MySQL数据库和一个Oracle数据库 二、依赖 pom.xml <dependencies><dependency><groupId>org.springframework.boot</groupId&…

【Golang】go条件编译

交叉编译只是为了能在一个平台上编译出其他平台可运行的程序&#xff0c;Go 作为一个跨平台的语言&#xff0c;它提供的类库势必也是跨平台的&#xff0c;比如说程序的系统调用相关的功能&#xff0c;能根据所处环境选择对应的源码进行编译。让编译器只对满足条件的代码进行编译…

【Linux】centos8安装cmake3.27.4

第一步&#xff0c;去官网下安装包&#xff0c;一定不要下错了 下好了之后&#xff0c;用ftp软件传到云服务器或者虚拟机上&#xff0c;我用的是centos8系统&#xff0c;安装之前先准备好这些依赖项 yum install -y gcc gcc-c make automake yum install -y openssl openssl-…

多线程应用——单例模式

单例模式 文章目录 单例模式一.什么是单例模式二.如何实现1.口头实现2.利用语法特性 三.实现方式&#xff08;饿汉式懒汉式&#xff09;1.饿汉式2.懒汉式3.线程安全的单例模式4.双重检查锁5.禁止指令重排序 一.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff…

LLM本地知识库问答系统(二):如何正确使用LlamaIndex索引

推荐阅读列表&#xff1a; LLM本地知识库问答系统&#xff08;一&#xff09;&#xff1a;使用LangChain和LlamaIndex从零构建PDF聊天机器人指南 上一篇文章我们介绍了使用LlamaIndex构建PDF聊天机器人&#xff0c;本文将介绍一下LlamaIndex的基本概念和原理。 LlamaIndex简介…

视频分割合并工具说明

使用说明书&#xff1a;视频分割合并工具 欢迎使用视频生成工具&#xff01;本工具旨在帮助您将视频文件按照指定的规则分割并合并&#xff0c;以生成您所需的视频。 本程序还自带提高分辨率1920:1080&#xff0c;以及增加10db声音的功能 软件下载地址 https://github.com/c…

FPGA原理与结构——时钟IP核原理学习

一、前言 在之前的文章中&#xff0c;我们介绍了FPGA的时钟结构 FPGA原理与结构——时钟资源https://blog.csdn.net/apple_53311083/article/details/132307564?spm1001.2014.3001.5502 在本文中我们将学习xilinx系列的FPGA所提供的时钟IP核&#xff0c;来帮助我们进一…