以前
以前调试的时候,经常会用到一系列进程启动,需要附加到某一个进程,这个进程可能被其他程序唤起的,又很快就结束了,也不能直接执行这个程序复现情况,必须要在当时的现场中运行。
比如这样的办法做一个跳板:
#!/bin/env python3
import os
import sys
import time
with open("/dev/pts/13", "w") as f:
f.write("%d" % os.getpid())
time.sleep(10)
os.execve("/usr/libexec/gdb", sys.argv, os.environ)
上面的步骤中,/dev/pts/13是另一个终端,要把gdb的pid发送到另一个终端中,方便另一个终端中使用gdb附加到这个出错的gdb中。
然后sleep(10)为了留出时间附加,在接下来execve将会将这个跳板替换为真正的gdb程序
接下来,替换这个脚本到/bin/gdb,/bin/gdb本身也只是一个软连接,指向的是/usr/libexec/gdb,另一个终端等待收到gdb的pid进行附加
这种办法还是麻烦了一丢丢,有没有更强的办法呢???
现在
假如,我要附加ls
命令!
第一步,找到合适的附加位置和过滤条件
另一个窗口等下执行ls
# ls
看到comm
就是ls
,就用这个当过滤条件吧。
# bpftrace -e 'tracepoint:syscalls:sys_exit_execve {printf("%s\n", comm);}'
Attaching 1 probe...
ls
ls
命令被执行时,在execve
系统调用返回时,整个进程空间替换完成,execve
准备由加载器装载动态库还没有执行到main
,正是时候。
# bpftrace -e 'tracepoint:syscalls:sys_exit_execve / comm == "ls" / {signal(20); printf("%d %s\n", pid, comm); exit();}' --unsafe
Attaching 1 probe...
2210 l
接下来,还是附加到sys_exit_execve
,过滤条件comm == "ls"
,然后发送信号20
,即SIGTSTP
,和终端中敲ctrl + z
一样,暂停进程,则会在execve hock
时同步执行,保证hock的时效性,接下来的printf
和exit
是异步,由bpftrace
返回到用户态时执行,打印下pid
方便等下gdb
附加,exit
退出bpftrace
。
# gdb -p 2210
(gdb) bt
#0 0x00007fd3d6b6ac00 in _start () from /lib64/ld-linux-x86-64.so.2
#1 0x0000000000000002 in ?? ()
#2 0x00007fff6c579d07 in ?? ()
#3 0x00007fff6c579d0a in ?? ()
#4 0x0000000000000000 in ?? ()
(gdb) info proc mappings
process 2210
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x55e415e00000 0x55e415e20000 0x20000 0x0 /usr/bin/ls
0x55e41601f000 0x55e416022000 0x3000 0x1f000 /usr/bin/ls
0x55e416022000 0x55e416023000 0x1000 0x0
0x7fd3d6b51000 0x7fd3d6b7f000 0x2e000 0x0 /usr/lib64/ld-2.28.so
0x7fd3d6d79000 0x7fd3d6d7d000 0x4000 0x0 [vvar]
0x7fd3d6d7d000 0x7fd3d6d7f000 0x2000 0x0 [vdso]
0x7fd3d6d7f000 0x7fd3d6d82000 0x3000 0x2e000 /usr/lib64/ld-2.28.so
0x7fff6c559000 0x7fff6c57b000 0x22000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
这个时候附加,效果嘎嘎好,就连libc
这种都没装载,和上面跳板方式效果几乎一样。
而且,可以不仅仅用在execve
,还可以任意自定义hock
时刻,比如读写文件时、权能变化时,这种方式bpftrace
用起来都非常方便。