动态断点
设计目标:移除断点
前面介绍了如何添加断点、显示断点列表,现在我们来看看如何移除断点。
移除断点与新增断点,都是需要借助ptrace来实现。回想下新增断点首先通过PTRACEPEEKDATA/PTRACEPOKEDATA来实现对指令数据的备份、覆写,移除断点的逻辑有点相反,先将原来备份的指令数据覆写回断点对应的指令地址处,然后,从已添加断点集合中移除即可。
ps: 在Linux下PTRACE_PEEKTEXT/PTRACE_PEEKDATA,以及PTRACE_POKETEXT/PTRACE_POKEDATA并没有什么不同,所以执行ptrace操作的时候,ptrace request可以任选一个。
为了可读性,读写指令时倾向于PTRACE_PEEKTEXT/PTRACE_POKETEXT,读写数据时则倾向于PTRACE_PEEKDATA/PTRACE_POKEDATA。
代码实现
首先解析断点编号参数-n <breakNo>
,并从已添加断点集合中查询,是否有编号为n的断点存在,如果没有则<breakNo>
为无效参数。
如果断点确实存在,则执行ptrace(PTRACE_POKEDATA,...)将原来备份的1字节指令数据覆写回原指令地址,即消除了断点。然后,再从已添加断点集合中删除这个断点。
package debug
import (
"errors"
"fmt"
"strings"
"syscall"
"godbg/target"
"github.com/spf13/cobra"
)
var clearCmd = &cobra.Command{
Use: "clear <n>",
Short: "清除指定编号的断点",
Long: `清除指定编号的断点`,
Annotations: map[string]string{
cmdGroupKey: cmdGroupBreakpoints,
},
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("clear %s\n", strings.Join(args, " "))
id, err := cmd.Flags().GetUint64("n")
if err != nil {
return err
}
// 查找断点
var brk *target.Breakpoint
for _, b := range breakpoints {
if b.ID != id {
continue
}
brk = b
break
}
if brk == nil {
return errors.New("断点不存在")
}
// 移除断点
n, err := syscall.PtracePokeData(TraceePID, brk.Addr, []byte{brk.Orig})
if err != nil || n != 1 {
return fmt.Errorf("移除断点失败: %v", err)
}
delete(breakpoints, brk.Addr)
fmt.Println("移除断点成功")
return nil
},
}
func init() {
debugRootCmd.AddCommand(clearCmd)
clearCmd.Flags().Uint64P("n", "n", 1, "断点编号")
}
代码测试
首先运行一个待调试程序,获取其pid,然后通过godbg attach <pid>
调试目标进程,首先通过命令disass
显示汇编指令列表,然后执行b <locspec>
命令添加几个断点。
godbg> b 0x4653af
break 0x4653af
添加断点成功
godbg> b 0x4653b6
break 0x4653b6
添加断点成功
godbg> b 0x4653c2
break 0x4653c2
添加断点成功
这里我们执行了3次断点添加操作,breakpoints
可以看到添加的断点列表:
godbg> breakpoints
breakpoint[1] 0x4653af
breakpoint[2] 0x4653b6
breakpoint[3] 0x4653c2
然后我们执行clear -n 2
移除第2个断点:
godbg> clear -n 2
clear
移除断点成功
接下来再次执行breakpoints
查看剩余的断点:
godbg> bs
breakpoint[1] 0x4653af
breakpoint[3] 0x4653c2
现在断点2已经被移除了,我们的添加、移除断点的功能是正常的。