gdb 工作原理和内核实现

gdb主要功能的实现依赖于一个系统函数ptrace,通过man手册可以了解到,ptrace可以让父进程观察和控制其子进程的检查、执行,改变其寄存器和内存的内容,主要应用于打断点(也是gdb的主要功能)和打印系统调用轨迹。



一、ptrace函数
函数原型如下:
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data);
ptrace系统调用的request主要选项



PTRACE_TRACEME
表示本进程将被其父进程跟踪,交付给这个进程的所有信号(除SIGKILL之外),都将使其停止,父进程将通过wait()获知这一情况。
PTRACE_ATTACH
attach到一个指定的进程,使其成为当前进程跟踪的子进程,子进程的行为等同于它进行了一次PTRACE_TRACEME操作。
PTRACE_CONT
继续运行之前停止的子进程。可同时向子进程交付指定的信号。



更多参数请man ptrace



二、gdb使用ptrace的基本流程
gdb调试一个新进程:通过fork函数创建一个新进程,在子进程中执行ptrace(PTRACE_TRACEME, 0, 0, 0)函数,然后通过execv()调用准备调试的程序。
attach到已运行进程:将pid传递给gdb,然后执行ptrace(PTRACE_ATTACH, pid, 0, 0)。
在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用建立调试关系之后,交付给目标程序的任何信号(除SIGKILL之外)都将被gdb先行截获,gdb因此有机会对信号进行相应处理,并根据信号的属性决定在继续目标程序运行时是否将之前截获的信号实际交付给目标程序。



三、gdb使用的内核机制
断点的功能是通过内核信号实现的,以x86为例,内核向某个地址打入断点,实际上就是往该地址写入断点指令INT 3,即0xCC。目标程序运行到这条指令之后就会触发SIGTRAP信号,gdb捕获到这个信号,根据目标程序当前停止位置查询gdb维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。



内核是通过如下调用进入内核态的:



SYSCALL_DEFINE4(ptrace, long, request, long, pid, long, addr, long, data)



根据不同的request调用不同函数,基本都是判断当前进程task中ptrace选项,走security_ptrace函数,在linux security模块中,然后汇编。

gdb三种调试方式
1)attach并调试一个已经运行的进程:
确定需要进行调试的进程id
运行gdb,输入attch pid,如:gdb 12345。gdb将对指定进行执行如下操作:ptrace(PTRACE_ATTACH,pid,0,0)
2)运行并调试一个新的进程
运行gdb,通过命令行参数或file指定目标调试程序,如gdb ./test
输入run命令,gdb执行下述操作:
通过fork()系统调用创建一个新进程
在新创建的子进程中调用ptrace(PTRACE_TRACEME,0,0,0)
在子进程中通过execv()系统调用加载用户指定的可执行文件
3)远程调试目标主机上新创建的进程
gdb运行在调试机,gdbserver运行在目标机,通过二者之间定义的数据格式进行通信
gdb调试的基础—信号
  gdb调试的实现都是建立在信号的基础上的,在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用建立调试关系后,交付给目标程序的任何信号首先都会被gdb截获。
  因此gdb可以先行对信号进行相应处理,并根据信号的属性决定是否要将信号交付给目标程序。
  
  1、设置断点:   
  信号是实现断点的基础,当用breakpoint 设置一个断点后,gdb会在=找到该位置对应的具体地址,然后向该地址写入断点指令INT3,即0xCC。
  目标程序运行到这条指令时,就会触发SIGTRAP信号,gdb会首先捕获到这个信号。然后根据目标程序当前停止的位置在gdb维护的断点链表中查询,若存在,则可判定为命中断点。
  gdb暂停目标程序运行的方法是想起发送SIGSTOP信号。
  
  2、next单步调试:
  next指令可以实现单步调试,即每次只执行一行语句。一行语句可能对应多条及其指令,当执行next指令时,gdb会计算下一条语句对应的第一条指令的地址,然后控制目标程序走到该位置停止。
gdb调试的基础—信号
  gdb调试的实现都是建立在信号的基础上的,在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用建立调试关系后,交付给目标程序的任何信号首先都会被gdb截获。
  因此gdb可以先行对信号进行相应处理,并根据信号的属性决定是否要将信号交付给目标程序。
  
  1、设置断点:   
  信号是实现断点的基础,当用breakpoint 设置一个断点后,gdb会在=找到该位置对应的具体地址,然后向该地址写入断点指令INT3,即0xCC。
  目标程序运行到这条指令时,就会触发SIGTRAP信号,gdb会首先捕获到这个信号。然后根据目标程序当前停止的位置在gdb维护的断点链表中查询,若存在,则可判定为命中断点。
  gdb暂停目标程序运行的方法是想起发送SIGSTOP信号。
  
  2、next单步调试:
  next指令可以实现单步调试,即每次只执行一行语句。一行语句可能对应多条及其指令,当执行next指令时,gdb会计算下一条语句对应的第一条指令的地址,然后控制目标程序走到该位置停止。
  这里写图片描述



参考:
http://www.spongeliu.com/240.html



http://www.docin.com/p-18618736.html



1、GDB对于基于GNU系统开发的程序员来说是最基本的东西,必须的。所以这篇学习总结中,不打算包括GDB的一般使用方法。因为这些东西必须是随手拈来的。所以也就不花时间来整理,我只把一些比较高级的应用在这里作一个整理。



2、在编译链接程序时需要使用”-ggdb”选项来生成可供GDB调试用的信息,否则GDB将失去作用,因此GDB和GCC联系的非常紧密。并且当-g和-O开关同时打开时,调试和优化可能会产生冲突,经常会发现所见和事实不合的情况,所以要选择性地开启优化开关。



3、GDB的一些使用技巧:
1)设置断点的方法包括:函数,行号,if条件断点express,这些前面都可以跟上文件名。另外还可以设置地址断点:b *0x8048424.
2)GDB用来分析core文件,启动格式:gdb debugme core.xyz
3)开启core文件生成的方法是: ulimit -c unlimited
4)在不同函数的调用栈上切换及查看当前信息:bt/frame XX/up/down/info frame/args/locals
5)调试一正运行的进程:gdb debugme pid或者gdb debugme + attach pid + detach,类似的应用还有:strace/ltrace/truss
6)如果某个线程/进程处于死锁状态,还可以通过gcore pid来手动生成core文件来分析当前线程/进程的状态,然后利用GDB来分析, gcore使用方法:gcore pid,注意被调试的进程会临时性停止去生成core文件
7)查看函数的反汇编指令:disassemble fun_name
8)汇编指令级别的单步执行:ni/si,显示当前执行的汇编指令: x/i $pc
9)查看寄存器的内容:info registers/all-registers
10)查看某地址开始的内容:x/num 0xYYYYYYY 查看从0xYYYYYYY开始的num个单元内容;p 输出数组内容
11)在函数调试中途强制返回:return ;
12)向被调试程序发送指定信号:在任意一点ctrl+C进入gdb调试命令行,然后:signal 1-15



4、用GDB来调试多线程程序:
1)显示当前可调试的所有线程:info threads,GDB按照线程启动顺序重新安排了一个线程ID,这个ID是供GDB使用的
2)在调试多线程的程序时,默认调试的是主线程,其他线程也同时处于暂停状态,如果想切换调试其他的线程,则只需要:thread id
3)在对某一线程进行next/step执行的时候,其他线程也同时在执行,如果要限制其他线程执行,则可以使用:set scheduler-locking on
4)对指定线程或者所有线程执行同样的操作,比如查看调用栈信息:thread apply ID1 ID2/all bt
5)另外你也可以利用strace -p pid来显示某个线程当前的系统调用情况。或者利用gdb debugme pid来调试某个线程,但注意该方法会暂停整个进程的执行。对于多线程的程序gdb ./debugme相当于默认调试主线程,而gdb ./debugme pid则相当于默认调试pid线程。



5、用GDB来调试多进程程序:
1)当fork子进程后,继续调试父进程或者调试刚产生的子进程:set follow-fork-mode parent/child,注意调试的时候其他的进程仍然在运行。
2)如果父进程fork了多个子进程,上面的这种方法也只能跟踪调试到第一个子进程,并且不影响其他子进程的运行。
3)如果想在调试一个进程的时候,其他进程处于暂停状态,则可以利用:set detach-on-fork off来做到
4)利用attach来调试子进程。因为父进程fork子进程后,子进程会马上得到执行,如果恰好执行过了你要调试的地方,则来不及查询pid并且attach,所以为了支持直接attach调试,一般会在子进程的代码开始处加上一个sleep,以使得你有时间来查询pid,然后attach进入来调试。
attach pid + stop + break XXX + continue + n + n …+ s + s + ….
5)利用gdb ./debugme pid都可以用来调试进程和线程,但不同的是GDB控制的范围不一样,前者不影响其他的并行单元(进程),而后则会使真个进程暂停。



6、调试动态链接库函数:
我们可能要调试动态库的函数,或者通过调试来学习动态库函数的实现。这个时候,则需要GDB包括该动态库的debug版本,否则在GDB下面只会打印:0xXXXXXX: ??
比如包括:glibc debug version,如下是一些glibc的debug版本的下载地址:
http://linux.maruhn.com/sec/glibc-debug.html



注:GDB的远端调试功能,暂时还没有接触过,现不做学习和总结.
GDB对于多线程,多进程的调试支持并不强大,但可以利用其他专用调试器,比如TotalView:
参考地址:http://www.totalviewtech.com/
http://www.total-view.com.cn/



7、一些辅助的诊断及调试工具:
1)strace:跟踪系统调用情况
2)ltrace:跟踪动态库的调用情况
3)mtrace,pmalloc:跟踪内存使用情况,需要嵌入代码,打印内存使用记录。
4)Binuitls:Toolchain的工具,参考我的上一篇总结。
5)Valgrind:非常好的内存泄露检测工具,限于i386
6)oprofile, NPTL Trace Tool等
7)ald:汇编语言调试器
8)Dude:另一个运行linux上的调试器,未使用ptrace实现
9)Linice(http://www.linice.com/)是SoftIce在Linux中的模拟软件,用于调试没有源代码的二进制文件的内核级调试器。
10)其他
关于调试及诊断工具包括许多,估计可以写一系列的文章来说明。



其他参考资料:
0)GDB官方网站:http://www.gnu.org/software/gdb/gdb.html
1)快速参考GDB支持的所有调试命令:《GDB QUICK REFERENCE》
2)GDB的使用手册:《Debugging with gdb–The gnu Source-Level Debugger》
3)《Embedded linux prime》的第13/14/15章可以作为参考。
http://book.opensourceproject.org.cn/embedded/embeddedprime/index.html?page=opensource/0136130550/ch13lev1sec1.html
文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/6_system/linux/Linuxjs/20091209/184488.html






1、GDB基本组成:
GDB由三个部分组成:
(1)用户接口user interface,除支持传统的CLI接口还支持mi接口(ddd等工具使用)
(2)符号处理层symbol handling,当gdb ./debugme后GDB会读取文件的符号信息,之后的原代码,变量/函数/类型的显示都由该部分进行(everything you can do without live process)。
(3)目标系统处理层target system handling。包括执行控制,断点设置,单步执行,堆栈分析等操作都有该部分来进行。



2、GDB各部分的实现:
(1)用户接口层(CLI)的实现很显然要用到readline/history库,而图形界面mi则需要用到:GNU ncurses库。
参考资料: http://www.gnu.org/software/ncurses/
http://tiswww.case.edu/php/chet/readline/rltop.html



(2)符号处理层则需要使用到:BFD/Opcodes库,分别用来读取分析ELF/Core文件,反汇编.
参考资料: http://www.xfocus.net/articles/200109/265.html
http://sourceware.org/binutils/docs/bfd/index.html#Top
http://www.linuxselfhelp.com/gnu/bfd/html_chapter/bfd_toc.html



(3)目标系统控制层:用ptrace系统调用来实现对其他进程的执行控制,检查和改变其核心映像以及寄存器等操作。



3、后端(目标系统控制层)实现:
(1)内核在执行用户请求的系统调用之前回检查当前进程是否处于被“跟踪”状态,如果是的话内核暂停当前进程并将控制权交给调试进程,使跟踪调试进程可以查看甚至修改被调试进程的内存,寄存器等数据。而ptrace函数的作用就是告诉内核在执行子进程的系统调用之前做的动作。所有的动作都可以通过request进行传入。
(2)设置断点原理:通过查找输入的断点和具体代码位置对应起来,并在该位置替换为一条断点指令,并且保存以前的指令,到目标程序运行到该断点处时,产生SIGTRAP信号,该信号被GDB捕获,GDB查找断点列表来确定是否命中断点。继续执行的时候则会把保存的指令重新放回并执行。n/s/ni/si/finish/uitil也会自动设置断点。
(3)内核传递给被调试进程所有的信号,都会先传递给GDB再由gdb采取定义的动作来和被调试进程之间进行相互协调操作。gdb暂停目标程序运行的方法是向其发送SIGSTOP信号,GDB对于随机信号(非GDB产生的)的处理包括,可以通过handle signals命令来预定义



4、 ptrace函数简单介绍:long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);其中第一个参数代表告诉给kernel要做的动作。
PTRACE_ME:设置自己的被跟踪标志,在被调试进程中使用。
PTRACE_PEEKUSER:可以得到系统调用号及参数信息
PTRACE_CONT:使被跟踪进程继续执行
PRACE_GETREGS:一次性得到所有寄存器相关的值,提供输出参数
PTRACE_POKEDATA:可用来改变子进程中变量的值
PTRACE_SINGLESTEP:会使内核在子进程的每一条指令执行前先将其阻塞,然后将控制权交给父进程
PTRACE_ATTACH:向运行着的子进程置上跟踪标志为。
PTRACE_DETACH:和上面的行为相反。
很多工具strace/ltrace/stuss等工具都用到了ptrace,学习ptrace的最好的资料是这些工具的原代码和kernel相关代码。



linux的进程状态大体分为以下几种:



D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
R (TASK_RUNNING),进程执行中。
S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
T (TASK_STOPPED),暂停状态。
t (TASK_TRACED),进程被追踪。
w (TASK_PAGING),进程调页中,2.6以上版本的内核中已经被移除。
X (TASK_DEAD – EXIT_DEAD),退出状态,进程即将被销毁。
Z (TASK_DEAD – EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
(以上内容来自ps命令的manual手册,原文请看↓)



其中上面的5就是我们要讨论的,gdb调试程序时的t状态,程序被追踪。(关于进程的其他状态请自行百度)。



请看ptrace系统调用手册↓



ptrace的原型可以看到是:



long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);



4个参数的含义分别为:



enum __ptrace_request request:指示了ptrace要执行的命令。
pid_t pid: 指示ptrace要跟踪的进程。
void *addr: 指示要监控的内存地址。
void *data: 存放读取出的或者要写入的数据。
描述译文如下:



ptrace()系统调用提供了一个方法,该方法使一个程序(追踪者)可以观察和控制另外一个程序(被追踪者)的执行,并检查和改变被追踪者的内存及寄存器。它主要用于实现断点调试和追踪系统调用。



被追踪者首先需要被追踪者attach(这个词实在不知道咋翻译了……但是程序员应该都懂@_@)。该行为以及后续操作是线程独立的:在一个多线程的进程中,每一个线程可以被一个独立的(可能是不同的)追踪者attach,或者干脆不理会。因此,被追踪者永远是“一个线程”,而不是一个(可能是多线程的)进程。使用ptrace命令的方法是追踪程序发送如下命令给被追踪程序:



ptrace(PTRACE_foo, pid, …)



pid即linux系统分配的线程号。



当被追踪时,被追踪线程在接收信号时会被停止,即使那个信号是被忽略的也是如此(SIGKILL除外)。追踪程序会在一个调用waitpid(或者其他类wait系统调用)时收到通知,该调用会返回一个包含被追踪线程停止的原因的状态值。当被追踪线程停止时,追踪程序可以使用多种ptrace请求来检查和编辑被追踪线程。追踪程序可以让被追踪线程继续运行,有选择地忽略发过来的信号(甚至可以发送一个完全不同的信号给被追踪线程)。



可以看到,ptrace确实是一个强大的系统调用;gdb就是基于ptrace这个系统调用来做的。其原理是利用ptrace系统调用,在被调试程序和gdb之间建立追踪关系。然后所有发送给被调试程序(被追踪线程)的信号(除SIGKILL)都会被gdb截获,gdb根据截获的信号,查看被调试程序相应的内存地址,并控制被调试的程序继续运行。GDB常用的使用方法有断点设置和单步调试,接下来我们来分析一下他们是如何实现的。



1.建立调试关系:



用gdb调试程序有2种模式,包括使用gdb启动程序,以及attach到现有进程。分别对应下面2种建立调试关系的方法:



1) fork: 利用fork+execve执行被测试的程序,子进程在执行execve之前调用ptrace(PTRACE_TRACEME),建立了与父进程(debugger)的跟踪关系。



2) attach: debugger可以调用ptrace(PTRACE_ATTACH,pid,…),建立自己与进程号为pid的进程间的跟踪关系。即利用PTRACE_ATTACH,使自己变成被调试程序的父进程(用ps可以看到)。用attach建立起来的跟踪关系,可以调用ptrace(PTRACE_DETACH,pid,…)来解除。注意attach进程时的权限问题,如一个非root权限的进程是不能attach到一个root进程上的。



2.断点原理:



1) 断点的实现原理,就是在指定的位置插入断点指令,当被调试的程序运行到断点的时候,产生SIGTRAP信号。该信号被gdb捕获并进行断点命中判定,当gdb判断出这次SIGTRAP是断点命中之后就会转入等待用户输入进行下一步处理,否则继续。



2) 断点的设置原理: 在程序中设置断点,就是先将该位置的原来的指令保存,然后向该位置写入int 3。当执行到int 3的时候,发生软中断,内核会给子进程发出SIGTRAP信号,当然这个信号会被转发给父进程。然后用保存的指令替换int3,等待恢复运行。



3) 断点命中判定:gdb把所有的断点位置都存放在一个链表中,命中判定即把被调试程序当前停止的位置和链表中的断点位置进行比较,看是断点产生的信号,还是无关信号。



4) 条件断点的判定:原理同3),只是恢复断点处的指令后,再多加一步条件判断。若表达式为真,则触发断点。由于需要判断一次,因此加入条件断点后,不管有没有触发到条件断点,都会影响性能。在x86平台,某些硬件支持硬件断点,在条件断点处不插入int 3,而是插入一个其他指令,当程序走到这个地址的时候,不发出int 3信号,而是先去比较一下特定寄存器和某个地址的内容,再决定是否发送int 3。因此,当你的断点的位置会被程序频繁地“路过”时,尽量使用硬件断点,会对提高性能有帮助。



3.单步跟踪原理:



这个最简单,因为ptrace本身支持单步功能,调用ptrace(PTRACE_SINGLESTEP,pid,…)即可



ptrace系统调用longptrace(enum__ptrace_requestrequest,pid_tpid,voidaddr,voiddata);request参数主要选项PTRACE_TRACEME表示本进程将被其父进程跟踪,交付给这个进程的所有信号(除SIGKILL之外),都将使其停止,父进程通过wait()获知这一情况。PTRACE_ATTACHattach到一个指定的进程,使其成为当前进程跟踪的子进程,子进程的行为等同于它进行了一次PTRACE_TRACE



ptrace 系统调用
long ptrace(enum __ptrace_request request, pid_t pid,void *addr,void *data);
request 参数主要选项



PTRACE_TRACEME
表示本进程将被其父进程跟踪,交付给这个进程的所有信号(除 SIGKILL 之外),都将使其停止,父进程通过 wait() 获知这一情况。



PTRACE_ATTACH
attach 到一个指定的进程,使其成为当前进程跟踪的子进程,子进程的行为等同于它进行了一次 PTRACE_TRACEME 操作。



PTRACE_CONT
继续运行之前停止的子进程。可同时向子进程交付指定的信号。



调试的基本流程
运行 gdb 启动调试程序,输入 run 命令,程序执行下述操作



通过 fork() 系统调用创建一个新进程
在新创建的子进程中调用 ptrace(PTRACE_TRACEME,0,0,0)
在子进程中通过 execv() 系统调用加载用户指定的可执行文件
在使用 ptrace 系统调用建立调试关系后,目标程序收到的任何信号首先都会被 GDB 截获,因此 GDB 可以对目标程序的运行进行控制。



断点
当用 breakpoint 设置一个断点后,gdb 会找到该位置对应的具体地址,然后向该地址写入断点指令 INT3。
目标程序运行到这条指令时,就会触发 SIGTRAP 信号,gdb 会首先捕获到这个信号,然后根据目标程序当前停止的位置在 gdb 维护的断点链表中查询,若存在,则可判定为命中断点。
gdb 暂停目标程序运行的方法是想起发送 SIGSTOP 信号。
next 指令可以实现单步调试,即每次只执行一行语句。一行语句可能对应多条及其指令,当执行next指令时,gdb 会计算下一条语句对应的第一条指令的地址,然后控制目标程序走到该位置停止。


Category golang
Error: Comments Not Initialized

泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720
泽民博客
zemin
2025-01-31 01:41:43.720