diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 7f4aaa33..afb3ea93 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -640,6 +640,7 @@ func init() { rootCmd.PersistentFlags().IntVar(&gconfig.BrkPid, "brk-pid", -1, "set hardware breakpoint pid, just keep default") rootCmd.PersistentFlags().StringVar(&gconfig.BrkLib, "brk-lib", "", "as library base address, work with -p/--pid option") rootCmd.PersistentFlags().Uint64Var(&gconfig.BrkLen, "brk-len", 4, "hardware breakpoint length, default 4, support [1, 8]") + rootCmd.PersistentFlags().StringArrayVar(&gconfig.BrkPoint, "brk-point", []string{}, "read args when hardware breakpoint hits, e.g. buf:128:x0 or [int:x1,buf:64:x0+8.ptr32+0x10]") // 缓冲区大小设定 单位M rootCmd.PersistentFlags().Uint32VarP(&gconfig.Buffer, "buffer", "b", 8, "perf cache buffer size, default 8M") rootCmd.PersistentFlags().Uint32Var(&gconfig.MaxOp, "maxop", 64, "max operation count for uprobe, at least 192 for string array") @@ -661,10 +662,11 @@ func init() { rootCmd.PersistentFlags().StringVar(&gconfig.ParseFile, "parse", "", "parse perf data as json or readable format") // 常规ELF库hook设定 rootCmd.PersistentFlags().StringVarP(&gconfig.Library, "lib", "l", "libc.so", "lib name or lib full path, default is libc.so") - rootCmd.PersistentFlags().StringArrayVarP(&gconfig.HookPoint, "point", "w", []string{}, "hook point config, e.g. strstr+0x0[str,str] write[int,buf:128,int]") + rootCmd.PersistentFlags().StringArrayVarP(&gconfig.HookPoint, "point", "w", []string{}, "hook point config, e.g. strstr+0x0[str,str] write[int,buf:128:x0+0x8.ptr32+0x10]") rootCmd.PersistentFlags().StringVar(&gconfig.RegName, "reg", "", "get the offset of reg") rootCmd.PersistentFlags().BoolVarP(&gconfig.DumpRet, "dumpret", "", false, "dump ret offset for symbol") rootCmd.PersistentFlags().BoolVarP(&gconfig.DumpHex, "dumphex", "", false, "dump buffer as hex") + rootCmd.PersistentFlags().BoolVarP(&gconfig.DumpBase64, "dumpbase64", "", false, "dump buffer as base64") rootCmd.PersistentFlags().BoolVarP(&gconfig.ShowPC, "showpc", "", false, "show origin pc register value") rootCmd.PersistentFlags().BoolVarP(&gconfig.ShowTime, "showtime", "", false, "show event boot time info") rootCmd.PersistentFlags().BoolVarP(&gconfig.ShowUid, "showuid", "", false, "show process uid info") diff --git a/src/common/buffer.h b/src/common/buffer.h index f1dd35e4..44e10d35 100644 --- a/src/common/buffer.h +++ b/src/common/buffer.h @@ -197,7 +197,7 @@ static __always_inline int save_str_to_buf(event_data_t *event, void *ptr, u8 in static __always_inline int save_utf16_to_buf(event_data_t *event, void *ptr, u8 index) { // UTF16 最大字节数(必须是偶数) - int max_bytes = 512; + int max_bytes = MAX_BUF_READ_SIZE; // 直接调用 save_bytes_to_buf return save_bytes_to_buf(event, ptr, max_bytes, index); } diff --git a/src/types.h b/src/types.h index 654b8712..b788afe4 100644 --- a/src/types.h +++ b/src/types.h @@ -103,7 +103,9 @@ enum op_code_e OP_READ_STD_STRING, OP_READ_IL2CPP_STRING, OP_SAVE_STRING16, - OP_SAVE_PTR_STRING16 + OP_SAVE_PTR_STRING16, + OP_READ_POINTER32, + OP_READ_POINTER64 }; enum arm64_reg_e @@ -290,4 +292,4 @@ typedef struct file_info { u64 ctime; } file_info_t; -#endif \ No newline at end of file +#endif diff --git a/src/utils.h b/src/utils.h index f78588c5..c982b91b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -163,6 +163,32 @@ static __noinline u32 read_args(program_data_t* p, point_args_t* point_args, op_ bpf_probe_read_user(&op_ctx->pointer_value, PTR_SIZE, (void*)op_ctx->read_addr); } break; + case OP_READ_POINTER32: + { + u32 ptr = 0; + if (op->pre_code == OP_ADD_OFFSET) { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)(op_ctx->read_addr + op->value)); + } else if (op->pre_code == OP_SUB_OFFSET) { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)(op_ctx->read_addr - op->value)); + } else { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)op_ctx->read_addr); + } + op_ctx->pointer_value = ptr; + break; + } + case OP_READ_POINTER64: + { + u64 ptr = 0; + if (op->pre_code == OP_ADD_OFFSET) { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)(op_ctx->read_addr + op->value)); + } else if (op->pre_code == OP_SUB_OFFSET) { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)(op_ctx->read_addr - op->value)); + } else { + bpf_probe_read_user(&ptr, sizeof(ptr), (void*)op_ctx->read_addr); + } + op_ctx->pointer_value = ptr; + break; + } case OP_SAVE_POINTER: save_to_submit_buf(p->event, (void *)&op_ctx->pointer_value, sizeof(op_ctx->pointer_value), op_ctx->save_index); op_ctx->save_index += 1; diff --git a/tests/config_uprobe_test_base64.json b/tests/config_uprobe_test_base64.json new file mode 100644 index 00000000..18d091af --- /dev/null +++ b/tests/config_uprobe_test_base64.json @@ -0,0 +1,18 @@ +{ + "type": "uprobe", + "library": "libtest.so", + "points": [ + { + "name": "testBytesBase64", + "params": [ + { + "name": "buf_bytes", + "type": "buf", + "reg": "x0", + "size": "x1", + "format": "base64" + } + ] + } + ] +} \ No newline at end of file diff --git a/user/argtype/argtype_base.go b/user/argtype/argtype_base.go index 4d3688c8..192eef72 100644 --- a/user/argtype/argtype_base.go +++ b/user/argtype/argtype_base.go @@ -587,6 +587,9 @@ func (this *ARG_BUFFER) ParseArg(ptr uint64, buf *bytes.Buffer, parse_more, fmt_ (this.ParseImpl).(IArgBuffer).SetArgPayload(payload) } if !fmt_json { + if this.DumpBase64 { + return fmt.Sprintf("0x%x%s", ptr, this.ParseImpl.HexToBase64Format()) + } if this.DumpHex { return fmt.Sprintf("0x%x%s", ptr, this.ParseImpl.HexFormat(this.Color)) } diff --git a/user/argtype/config_struct.go b/user/argtype/config_struct.go index 24f24ee2..1369f294 100644 --- a/user/argtype/config_struct.go +++ b/user/argtype/config_struct.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/json" + "encoding/base64" "fmt" "net" "stackplz/user/util" @@ -50,6 +51,7 @@ type IParseStruct interface { GetArgStruct() *Arg_struct Format() string HexFormat(bool) string + HexToBase64Format() string } // 结构体类型 @@ -115,6 +117,10 @@ func (this *Arg_buffer) HexFormat(color bool) string { return "()" } +func (this *Arg_buffer) HexToBase64Format() string { + return fmt.Sprintf(" (base64:%s)", base64.StdEncoding.EncodeToString(this.ArgPayload)) +} + func (this *Arg_buffer) MarshalJSON() ([]byte, error) { type ArgStructAlias Arg_struct return json.Marshal(&struct { @@ -143,6 +149,10 @@ func (this *Arg_string) HexFormat(color bool) string { return this.Format() } +func (this *Arg_string) HexToBase64Format() string { + return this.Format() +} + func (this *Arg_string) Format() string { return fmt.Sprintf("(%s)", util.B2STrim(this.ArgPayload)) } @@ -190,6 +200,10 @@ func (this *Arg_string16) HexFormat(color bool) string { return this.Format() } +func (this *Arg_string16) HexToBase64Format() string { + return this.Format() +} + func (this *Arg_string16) Format() string { // UTF‑16LE → UTF‑8 s := utf16leToUtf8(this.ArgPayload) @@ -237,6 +251,10 @@ func (this *Arg_Sigaction) HexFormat(color bool) string { return this.Format() } +func (this *Arg_Sigaction) HexToBase64Format() string { + return this.Format() +} + func (this *Arg_Sigaction) Format() string { var fields []string fields = append(fields, fmt.Sprintf("sa_handler=0x%x", this.Sa_handler)) @@ -288,6 +306,10 @@ func (this *Arg_Timespec) HexFormat(color bool) string { return this.Format() } +func (this *Arg_Timespec) HexToBase64Format() string { + return this.Format() +} + func (this *Arg_Timespec) Format() string { var fields []string fields = append(fields, fmt.Sprintf("sec=%d", this.Sec)) diff --git a/user/argtype/iargtype.go b/user/argtype/iargtype.go index 90d5de2c..c999c9d0 100644 --- a/user/argtype/iargtype.go +++ b/user/argtype/iargtype.go @@ -25,8 +25,10 @@ type IArgType interface { GetTypeIndex() uint32 SetParentIndex(uint32) SetDumpHex(bool) + SetDumpBase64(bool) SetColor(bool) GetDumpHex() bool + GetDumpBase64() bool GetColor() bool GetParentIndex() uint32 GetSize() uint32 @@ -54,6 +56,7 @@ type ArgType struct { ParseCB ParseFN ParseImpl IParseStruct DumpHex bool + DumpBase64 bool Color bool } @@ -77,6 +80,7 @@ func (this *ArgType) Clone() IArgType { at.ParseCB = this.ParseCB at.ParseImpl = this.ParseImpl at.DumpHex = this.DumpHex + at.DumpBase64 = this.DumpBase64 at.Color = this.Color return &at } @@ -105,6 +109,10 @@ func (this *ArgType) SetDumpHex(dump_hex bool) { this.DumpHex = dump_hex } +func (this *ArgType) SetDumpBase64(dump_base64 bool) { + this.DumpBase64 = dump_base64 +} + func (this *ArgType) SetColor(color bool) { this.Color = color } @@ -113,6 +121,10 @@ func (this *ArgType) GetDumpHex() bool { return this.DumpHex } +func (this *ArgType) GetDumpBase64() bool { + return this.DumpBase64 +} + func (this *ArgType) GetColor() bool { return this.Color } diff --git a/user/argtype/op_helper.go b/user/argtype/op_helper.go index 7c9caf95..99789f5b 100644 --- a/user/argtype/op_helper.go +++ b/user/argtype/op_helper.go @@ -40,6 +40,8 @@ const ( OP_READ_IL2CPP_STRING OP_SAVE_STRING16 OP_SAVE_PTR_STRING16 + OP_READ_POINTER32 + OP_READ_POINTER64 ) type BaseOpConfig struct { @@ -299,6 +301,8 @@ var OPC_READ_STD_STRING = ROP("READ_STD_STRING", OP_READ_STD_STRING) var OPC_READ_IL2CPP_STRING = ROP("READ_IL2CPP_STRING", OP_READ_IL2CPP_STRING) var OPC_SAVE_STRING16 = ROP("OPC_SAVE_STRING16", OP_SAVE_STRING16) var OPC_SAVE_PTR_STRING16 = ROP("SAVE_PTR_STRING16", OP_SAVE_PTR_STRING16) +var OPC_READ_POINTER32 = ROP("READ_POINTER32", OP_READ_POINTER32) +var OPC_READ_POINTER64 = ROP("READ_POINTER64", OP_READ_POINTER64) func BuildReadRegBreakCount(reg_index uint64) *OpConfig { op := OpConfig{} diff --git a/user/common/const_forarm.go b/user/common/const_forarm.go index 83527847..53151ccc 100644 --- a/user/common/const_forarm.go +++ b/user/common/const_forarm.go @@ -5,6 +5,8 @@ package common import "fmt" +const NativePointerSize = 4 + func GetRegIndex(reg string) uint32 { value, ok := RegsArmNameMap[reg] if !ok { diff --git a/user/common/const_forarm64.go b/user/common/const_forarm64.go index 766e6f54..5a73e35c 100644 --- a/user/common/const_forarm64.go +++ b/user/common/const_forarm64.go @@ -5,6 +5,8 @@ package common import "fmt" +const NativePointerSize = 8 + func GetRegIndex(reg string) uint32 { value, ok := RegsNameMap[reg] if !ok { diff --git a/user/config/config_brk.go b/user/config/config_brk.go new file mode 100644 index 00000000..23fb1787 --- /dev/null +++ b/user/config/config_brk.go @@ -0,0 +1,90 @@ +package config + +import ( + "errors" + "fmt" + "regexp" + "strings" + + . "stackplz/user/common" +) + +type BrkPointConfig struct { + ArgsStr string + PointArgs []*PointArg + DumpHex bool + DumpBase64 bool + Color bool +} + +func (this *BrkPointConfig) IsEnable() bool { + return len(this.PointArgs) > 0 +} + +func (this *BrkPointConfig) SetDumpHex(dumpHex bool) { + this.DumpHex = dumpHex +} + +func (this *BrkPointConfig) SetDumpBase64(dumpBase64 bool) { + this.DumpBase64 = dumpBase64 +} + +func (this *BrkPointConfig) SetColor(color bool) { + this.Color = color +} + +func (this *BrkPointConfig) Parse_BrkPoint(configs []string) error { + if len(configs) == 0 { + return nil + } + parser := &StackUprobeConfig{} + parser.SetDumpHex(this.DumpHex) + parser.SetDumpBase64(this.DumpBase64) + parser.SetColor(this.Color) + + var allArgs []string + for _, configStr := range configs { + configStr = strings.TrimSpace(configStr) + if configStr == "" { + continue + } + argsStr, err := extractBrkArgs(configStr) + if err != nil { + return err + } + for _, argStr := range strings.Split(argsStr, ",") { + argStr = strings.TrimSpace(argStr) + if argStr != "" { + allArgs = append(allArgs, argStr) + } + } + } + + this.ArgsStr = strings.Join(allArgs, ",") + for argIndex, argStr := range allArgs { + argName := fmt.Sprintf("arg_%d", argIndex) + pointArg := NewUprobePointArg(argName, POINTER, uint32(argIndex)) + if err := parser.ParseArgType(argStr, pointArg); err != nil { + return err + } + this.PointArgs = append(this.PointArgs, pointArg) + } + return nil +} + +func extractBrkArgs(configStr string) (string, error) { + if strings.HasPrefix(configStr, "[") && strings.HasSuffix(configStr, "]") { + return strings.TrimSpace(configStr[1 : len(configStr)-1]), nil + } + + reg := regexp.MustCompile(`\[(.+)\]`) + match := reg.FindStringSubmatch(configStr) + if len(match) == 2 { + return strings.TrimSpace(match[1]), nil + } + + if strings.Contains(configStr, "[") || strings.Contains(configStr, "]") { + return "", errors.New(fmt.Sprintf("parse brk point args failed: %s", configStr)) + } + return configStr, nil +} diff --git a/user/config/config_file.go b/user/config/config_file.go index c1771d08..288a4949 100644 --- a/user/config/config_file.go +++ b/user/config/config_file.go @@ -129,6 +129,8 @@ func (this *ParamConfig) GetPointArg(arg_index, point_type uint32) *PointArg { } switch this.Format { + case "base64": + point_arg.SetDumpBase64(true) case "hex": point_arg.SetHexFormat() case "hexdump": @@ -156,55 +158,7 @@ func (this *ParamConfig) GetPointArg(arg_index, point_type uint32) *PointArg { // 4. 在上一步结果上 -4+0x16 // 5. 以上一步结果作为读取地址 读取 64 字节数据 if this.ReadOp != "" { - // 即一系列 加、减、取指针 操作作为要读取类型的地址 通过以下规则来转换 - has_first_op := false - for ptr_idx, op_str := range strings.Split(this.ReadOp, ".") { - if ptr_idx > 0 { - point_arg.AddExtraOp(argtype.OPC_READ_POINTER) - point_arg.AddExtraOp(argtype.OPC_MOVE_POINTER_VALUE) - } - if op_str == "" { - continue - } - v := op_str + "+" - last_op := "" - for { - i := strings.IndexAny(v, "+-") - if i < 0 { - break - } - op := string(v[i]) - token := string(v[0:i]) - v = v[i+1:] - if token != "" { - if value, err := strconv.ParseUint(token, 0, 64); err == nil { - if !has_first_op { - panic(fmt.Sprintf("first op must be reg")) - } - if last_op == "-" { - point_arg.AddExtraOp(argtype.OPC_SUB_OFFSET.NewValue(value)) - } else { - point_arg.AddExtraOp(argtype.OPC_ADD_OFFSET.NewValue(value)) - } - } else { - reg_index := GetRegIndex(token) - point_arg.AddExtraOp(argtype.Add_READ_MOVE_REG(uint64(reg_index))) - if has_first_op { - if last_op == "-" { - point_arg.AddExtraOp(argtype.OPC_SUB_REG) - } else { - point_arg.AddExtraOp(argtype.OPC_ADD_REG) - } - } - if !has_first_op { - has_first_op = true - } - } - } - last_op = op - } - } - point_arg.AddExtraOp(argtype.OPC_SAVE_ADDR) + addReadOp(point_arg, this.ReadOp) } return point_arg } diff --git a/user/config/config_global.go b/user/config/config_global.go index 0a439dbe..7461541b 100644 --- a/user/config/config_global.go +++ b/user/config/config_global.go @@ -48,6 +48,7 @@ type GlobalConfig struct { BrkAddr string BrkLib string BrkLen uint64 + BrkPoint []string LogFile string DumpFile string ParseFile string @@ -58,6 +59,7 @@ type GlobalConfig struct { RegName string DumpRet bool DumpHex bool + DumpBase64 bool ShowPC bool ShowTime bool ShowUid bool diff --git a/user/config/config_module.go b/user/config/config_module.go index b98d2d1b..604f1ea4 100644 --- a/user/config/config_module.go +++ b/user/config/config_module.go @@ -27,6 +27,7 @@ type StackUprobeConfig struct { NonElfOffset uint64 Points []*UprobeArgs DumpHex bool + DumpBase64 bool Color bool } @@ -177,6 +178,7 @@ func (this *StackUprobeConfig) ParseArgType(arg_str string, point_arg *PointArg) } } at.SetDumpHex(this.DumpHex) + at.SetDumpBase64(this.DumpBase64) at.SetColor(this.Color) point_arg.SetTypeIndex(at.GetTypeIndex()) // 这个设定用于指示是否进一步读取和解析 @@ -205,55 +207,7 @@ func (this *StackUprobeConfig) ParseArgType(arg_str string, point_arg *PointArg) // 4. 在上一步结果上 -4+0x16 // 5. 以上一步结果作为读取地址 读取 64 字节数据 if read_op_str != "" { - // 即一系列 加、减、取指针 操作作为要读取类型的地址 通过以下规则来转换 - has_first_op := false - for ptr_idx, op_str := range strings.Split(read_op_str, ".") { - if ptr_idx > 0 { - point_arg.AddExtraOp(argtype.OPC_READ_POINTER) - point_arg.AddExtraOp(argtype.OPC_MOVE_POINTER_VALUE) - } - if op_str == "" { - continue - } - v := op_str + "+" - last_op := "" - for { - i := strings.IndexAny(v, "+-") - if i < 0 { - break - } - op := string(v[i]) - token := string(v[0:i]) - v = v[i+1:] - if token != "" { - if value, err := strconv.ParseUint(token, 0, 64); err == nil { - if !has_first_op { - panic(fmt.Sprintf("first op must be reg")) - } - if last_op == "-" { - point_arg.AddExtraOp(argtype.OPC_SUB_OFFSET.NewValue(value)) - } else { - point_arg.AddExtraOp(argtype.OPC_ADD_OFFSET.NewValue(value)) - } - } else { - reg_index := GetRegIndex(token) - point_arg.AddExtraOp(argtype.Add_READ_MOVE_REG(uint64(reg_index))) - if has_first_op { - if last_op == "-" { - point_arg.AddExtraOp(argtype.OPC_SUB_REG) - } else { - point_arg.AddExtraOp(argtype.OPC_ADD_REG) - } - } - if !has_first_op { - has_first_op = true - } - } - } - last_op = op - } - } - point_arg.AddExtraOp(argtype.OPC_SAVE_ADDR) + addReadOp(point_arg, read_op_str) } return err } @@ -266,6 +220,10 @@ func (this *StackUprobeConfig) SetDumpHex(dump_hex bool) { this.DumpHex = dump_hex } +func (this *StackUprobeConfig) SetDumpBase64(dump_base64 bool) { + this.DumpBase64 = dump_base64 +} + func (this *StackUprobeConfig) SetColor(color bool) { this.Color = color } @@ -443,6 +401,7 @@ type SyscallConfig struct { SysWhitelist []uint32 SysBlacklist []uint32 DumpHex bool + DumpBase64 bool Color bool } @@ -458,6 +417,10 @@ func (this *SyscallConfig) SetDumpHex(dump_hex bool) { this.DumpHex = dump_hex } +func (this *SyscallConfig) SetDumpBase64(dump_base64 bool) { + this.DumpBase64 = dump_base64 +} + func (this *SyscallConfig) SetColor(color bool) { this.Color = color } @@ -539,6 +502,7 @@ func (this *SyscallConfig) Parse_FileConfig(config *SyscallFileConfig) (err erro point_arg := param.GetPointArg(uint32(arg_index), point_type) point_arg.SetDumpHex(this.DumpHex) + point_arg.SetDumpBase64(this.DumpBase64) point_arg.SetColor(this.Color) a_p := point_arg.Clone() @@ -785,10 +749,12 @@ type ModuleConfig struct { BrkLen uint64 BrkType uint32 BrkKernel bool + BrkPointConf *BrkPointConfig Color bool DumpHandle *os.File FmtJson bool DumpHex bool + DumpBase64 bool ShowPC bool ShowTime bool ShowUid bool @@ -854,6 +820,7 @@ func (this *ModuleConfig) InitCommonConfig(gconfig *GlobalConfig) { this.FmtJson = gconfig.FmtJson this.RegName = gconfig.RegName this.DumpHex = gconfig.DumpHex + this.DumpBase64 = gconfig.DumpBase64 this.ShowPC = gconfig.ShowPC this.ShowTime = gconfig.ShowTime this.ShowUid = gconfig.ShowUid @@ -864,12 +831,22 @@ func (this *ModuleConfig) InitCommonConfig(gconfig *GlobalConfig) { this.StackUprobeConf = &StackUprobeConfig{} this.StackUprobeConf.SetDumpHex(this.DumpHex) + this.StackUprobeConf.SetDumpBase64((this.DumpBase64)) this.StackUprobeConf.SetColor(this.Color) + this.BrkPointConf = &BrkPointConfig{} + this.BrkPointConf.SetDumpHex(this.DumpHex) + this.BrkPointConf.SetDumpBase64(this.DumpBase64) + this.BrkPointConf.SetColor(this.Color) + if err := this.BrkPointConf.Parse_BrkPoint(gconfig.BrkPoint); err != nil { + panic(err) + } + this.SysCallConf = &SyscallConfig{} this.SysCallConf.SetDebug(this.Debug) this.SysCallConf.SetLogger(this.logger) this.SysCallConf.SetDumpHex(this.DumpHex) + this.SysCallConf.SetDumpBase64((this.DumpBase64)) this.SysCallConf.SetColor(this.Color) } diff --git a/user/config/config_point_arg.go b/user/config/config_point_arg.go index 9b453219..8e1aba0d 100644 --- a/user/config/config_point_arg.go +++ b/user/config/config_point_arg.go @@ -32,6 +32,10 @@ func (this *PointArg) SetDumpHex(dump_hex bool) { argtype.GetArgType(this.TypeIndex).SetDumpHex(dump_hex) } +func (this *PointArg) SetDumpBase64(dump_base64 bool) { + argtype.GetArgType(this.TypeIndex).SetDumpBase64(dump_base64) +} + func (this *PointArg) SetColor(color bool) { argtype.GetArgType(this.TypeIndex).SetColor(color) } diff --git a/user/config/config_read_op.go b/user/config/config_read_op.go new file mode 100644 index 00000000..c75c4d4f --- /dev/null +++ b/user/config/config_read_op.go @@ -0,0 +1,78 @@ +package config + +import ( + "fmt" + "strconv" + "strings" + + "stackplz/user/argtype" + . "stackplz/user/common" +) + +func addReadOp(pointArg *PointArg, readOp string) { + if readOp == "" { + return + } + + hasFirstOp := false + for ptrIdx, opStr := range strings.Split(readOp, ".") { + if ptrIdx > 0 { + readPtrOp, rest := parsePointerReadOp(opStr) + pointArg.AddExtraOp(readPtrOp) + pointArg.AddExtraOp(argtype.OPC_MOVE_POINTER_VALUE) + opStr = rest + } + if opStr == "" { + continue + } + v := opStr + "+" + lastOp := "" + for { + i := strings.IndexAny(v, "+-") + if i < 0 { + break + } + op := string(v[i]) + token := string(v[0:i]) + v = v[i+1:] + if token != "" { + if value, err := strconv.ParseUint(token, 0, 64); err == nil { + if !hasFirstOp { + panic(fmt.Sprintf("first op must be reg")) + } + if lastOp == "-" { + pointArg.AddExtraOp(argtype.OPC_SUB_OFFSET.NewValue(value)) + } else { + pointArg.AddExtraOp(argtype.OPC_ADD_OFFSET.NewValue(value)) + } + } else { + regIndex := GetRegIndex(token) + pointArg.AddExtraOp(argtype.Add_READ_MOVE_REG(uint64(regIndex))) + if hasFirstOp { + if lastOp == "-" { + pointArg.AddExtraOp(argtype.OPC_SUB_REG) + } else { + pointArg.AddExtraOp(argtype.OPC_ADD_REG) + } + } + if !hasFirstOp { + hasFirstOp = true + } + } + } + lastOp = op + } + } + pointArg.AddExtraOp(argtype.OPC_SAVE_ADDR) +} + +func parsePointerReadOp(opStr string) (*argtype.OpConfig, string) { + switch { + case opStr == "ptr32" || strings.HasPrefix(opStr, "ptr32+") || strings.HasPrefix(opStr, "ptr32-"): + return argtype.OPC_READ_POINTER32, strings.TrimPrefix(opStr, "ptr32") + case opStr == "ptr64" || strings.HasPrefix(opStr, "ptr64+") || strings.HasPrefix(opStr, "ptr64-"): + return argtype.OPC_READ_POINTER64, strings.TrimPrefix(opStr, "ptr64") + default: + return argtype.OPC_READ_POINTER, opStr + } +} diff --git a/user/config/config_read_op_test.go b/user/config/config_read_op_test.go new file mode 100644 index 00000000..d61d12e7 --- /dev/null +++ b/user/config/config_read_op_test.go @@ -0,0 +1,93 @@ +package config + +import ( + "testing" + + "stackplz/user/argtype" + . "stackplz/user/common" +) + +func TestAddReadOpPointerWidth(t *testing.T) { + tests := []struct { + name string + readOp string + pointerOp uint32 + offsets []uint64 + offsetOps []uint32 + terminalOp uint32 + }{ + { + name: "native pointer deref", + readOp: "sp+0x8.+0x10", + pointerOp: argtype.OP_READ_POINTER, + offsets: []uint64{0x8, 0x10}, + offsetOps: []uint32{argtype.OP_ADD_OFFSET, argtype.OP_ADD_OFFSET}, + terminalOp: argtype.OP_SAVE_ADDR, + }, + { + name: "explicit ptr32 deref", + readOp: "sp+0x8.ptr32+0x10", + pointerOp: argtype.OP_READ_POINTER32, + offsets: []uint64{0x8, 0x10}, + offsetOps: []uint32{argtype.OP_ADD_OFFSET, argtype.OP_ADD_OFFSET}, + terminalOp: argtype.OP_SAVE_ADDR, + }, + { + name: "explicit ptr64 deref with negative offset", + readOp: "sp+0x8.ptr64-0x4+0x20", + pointerOp: argtype.OP_READ_POINTER64, + offsets: []uint64{0x8, 0x4, 0x20}, + offsetOps: []uint32{argtype.OP_ADD_OFFSET, argtype.OP_SUB_OFFSET, argtype.OP_ADD_OFFSET}, + terminalOp: argtype.OP_SAVE_ADDR, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pointArg := NewUprobePointArg("arg_0", POINTER, 0) + addReadOp(pointArg, tt.readOp) + ops := opConfigs(pointArg.ExtraOpList) + + if len(ops) < 6 { + t.Fatalf("expected at least 6 ops, got %d", len(ops)) + } + if ops[0].Code != argtype.OP_READ_REG || ops[0].PostCode != argtype.OP_MOVE_REG_VALUE { + t.Fatalf("first op = %+v, want READ_REG/MOVE_REG_VALUE", ops[0]) + } + if ops[2].Code != tt.pointerOp { + t.Fatalf("pointer op = %s, want %s", argtype.OPM.GetOpName(ops[2].Code), argtype.OPM.GetOpName(tt.pointerOp)) + } + if ops[3].Code != argtype.OP_MOVE_POINTER_VALUE { + t.Fatalf("post pointer op = %s, want MOVE_POINTER_VALUE", argtype.OPM.GetOpName(ops[3].Code)) + } + if ops[len(ops)-1].Code != tt.terminalOp { + t.Fatalf("terminal op = %s, want %s", argtype.OPM.GetOpName(ops[len(ops)-1].Code), argtype.OPM.GetOpName(tt.terminalOp)) + } + + offsetIndex := 0 + for _, op := range ops { + if op.Code != argtype.OP_ADD_OFFSET && op.Code != argtype.OP_SUB_OFFSET { + continue + } + if offsetIndex >= len(tt.offsets) { + t.Fatalf("unexpected extra offset op: %+v", op) + } + if op.Code != tt.offsetOps[offsetIndex] || op.Value != tt.offsets[offsetIndex] { + t.Fatalf("offset op %d = %+v, want code=%s value=0x%x", offsetIndex, op, argtype.OPM.GetOpName(tt.offsetOps[offsetIndex]), tt.offsets[offsetIndex]) + } + offsetIndex++ + } + if offsetIndex != len(tt.offsets) { + t.Fatalf("matched %d offset ops, want %d", offsetIndex, len(tt.offsets)) + } + }) + } +} + +func opConfigs(opKeys []uint32) []*argtype.OpConfig { + ops := make([]*argtype.OpConfig, 0, len(opKeys)) + for _, opKey := range opKeys { + ops = append(ops, argtype.OPM.GetOp(opKey)) + } + return ops +} diff --git a/user/event/event_brk.go b/user/event/event_brk.go index dd9bfa23..9031866c 100644 --- a/user/event/event_brk.go +++ b/user/event/event_brk.go @@ -14,10 +14,14 @@ type BrkEvent struct { ContextEvent EventAddr uint64 UUID string + ArgStr string } func (this *BrkEvent) String() (s string) { s = fmt.Sprintf("[%s] event_addr:0x%x hit_count:%d", this.GetUUID(), this.EventAddr, hit_count) + if this.ArgStr != "" { + s += " args" + this.ArgStr + } s = this.GetStackTrace(s) return s } @@ -80,10 +84,19 @@ func (this *BrkEvent) ParseContext() (err error) { return err } this.ParseContextStack() + this.ParseBrkArgs() return nil } +func (this *BrkEvent) ParseBrkArgs() { + if this.mconf.BrkPointConf == nil || !this.mconf.BrkPointConf.IsEnable() { + return + } + reader := newBrkArgReader(this.GetPid(), this) + this.ArgStr = reader.formatArgs(this.mconf.BrkPointConf.PointArgs) +} + func (this *BrkEvent) Clone() IEventStruct { event := new(BrkEvent) return event diff --git a/user/event/event_brk_args.go b/user/event/event_brk_args.go new file mode 100644 index 00000000..3d57a330 --- /dev/null +++ b/user/event/event_brk_args.go @@ -0,0 +1,406 @@ +package event + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "os" + "strings" + + "stackplz/user/argtype" + "stackplz/user/common" + "stackplz/user/config" +) + +const maxStringReadSize = 16384 + +type brkArgReader struct { + pid uint32 + event *BrkEvent + mem *os.File +} + +type brkOpCtx struct { + saveIndex uint8 + regIndex uint8 + loopCount uint8 + breakCount uint8 + loopIndex int + opKeyIndex int + postCode uint32 + readLen uint32 + readAddr uint64 + regValue uint64 + pointerValue uint64 + tmpValue uint64 + saved bytes.Buffer +} + +func newBrkArgReader(pid uint32, event *BrkEvent) *brkArgReader { + return &brkArgReader{pid: pid, event: event} +} + +func (this *brkArgReader) Close() { + if this.mem != nil { + this.mem.Close() + this.mem = nil + } +} + +func (this *brkArgReader) formatArgs(pointArgs []*config.PointArg) string { + if len(pointArgs) == 0 { + return "" + } + defer this.Close() + + results := make([]string, 0, len(pointArgs)) + for _, pointArg := range pointArgs { + result, err := this.formatArg(pointArg) + if err != nil { + result = fmt.Sprintf("", err) + } + results = append(results, fmt.Sprintf("%s=%s", pointArg.Name, result)) + } + return "(" + strings.Join(results, ", ") + ")" +} + +func (this *brkArgReader) formatArg(pointArg *config.PointArg) (result string, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%v", r) + } + }() + + raw, err := this.runPointArg(pointArg) + if err != nil { + return "", err + } + buf := bytes.NewBuffer(raw) + var ptr argtype.Arg_reg + if err := binary.Read(buf, binary.LittleEndian, &ptr); err != nil { + return "", err + } + return pointArg.Parse(ptr.Address, buf, config.EBPF_UPROBE_ENTER), nil +} + +func (this *brkArgReader) runPointArg(pointArg *config.PointArg) ([]byte, error) { + ctx := &brkOpCtx{saveIndex: 0, opKeyIndex: 0, postCode: argtype.OP_SKIP} + opKeys := pointArg.GetOpList() + var currentOp *argtype.OpConfig + + for i := 0; i < common.MAX_OP_COUNT; i++ { + var code uint32 + if currentOp != nil && ctx.postCode != argtype.OP_SKIP { + code = ctx.postCode + ctx.postCode = argtype.OP_SKIP + } else { + if ctx.opKeyIndex >= len(opKeys) { + break + } + currentOp = argtype.OPM.GetOp(opKeys[ctx.opKeyIndex]) + ctx.opKeyIndex++ + code = currentOp.Code + ctx.postCode = currentOp.PostCode + } + + if code == argtype.OP_SKIP { + break + } + this.runOp(ctx, currentOp, code) + } + + if ctx.saved.Len() == 0 { + return nil, fmt.Errorf("no brk arg data saved") + } + return ctx.saved.Bytes(), nil +} + +func (this *brkArgReader) runOp(ctx *brkOpCtx, op *argtype.OpConfig, code uint32) { + switch code { + case argtype.OP_RESET_CTX: + ctx.breakCount = 0 + ctx.regIndex = 0 + ctx.readAddr = 0 + ctx.readLen = 0 + ctx.regValue = 0 + ctx.pointerValue = 0 + case argtype.OP_SET_REG_INDEX: + ctx.regIndex = uint8(op.Value) + case argtype.OP_SET_READ_LEN: + ctx.readLen = uint32(op.Value) + case argtype.OP_SET_READ_LEN_REG_VALUE: + if uint64(ctx.readLen) > ctx.regValue { + ctx.readLen = uint32(ctx.regValue) + } + case argtype.OP_SET_READ_LEN_POINTER_VALUE: + if uint64(ctx.readLen) > ctx.pointerValue { + ctx.readLen = uint32(ctx.pointerValue) + } + case argtype.OP_SET_READ_COUNT: + ctx.readLen *= uint32(op.Value) + case argtype.OP_ADD_OFFSET: + ctx.readAddr += op.Value + case argtype.OP_SUB_OFFSET: + ctx.readAddr -= op.Value + case argtype.OP_MOVE_REG_VALUE: + ctx.readAddr = ctx.regValue + case argtype.OP_MOVE_POINTER_VALUE: + ctx.readAddr = ctx.pointerValue + case argtype.OP_MOVE_TMP_VALUE: + ctx.readAddr = ctx.tmpValue + case argtype.OP_SET_TMP_VALUE: + ctx.tmpValue = ctx.readAddr + case argtype.OP_FOR_BREAK: + if ctx.loopCount == 0 { + ctx.loopIndex = ctx.opKeyIndex + } + if ctx.loopCount >= ctx.breakCount { + ctx.loopCount = 0 + ctx.breakCount = 0 + ctx.loopIndex = 0 + } else { + ctx.loopCount++ + ctx.opKeyIndex = ctx.loopIndex + } + case argtype.OP_SET_BREAK_COUNT: + ctx.breakCount = common.MAX_LOOP_COUNT + if uint64(ctx.breakCount) > op.Value { + ctx.breakCount = uint8(op.Value) + } + case argtype.OP_SET_BREAK_COUNT_REG_VALUE: + ctx.breakCount = common.MAX_LOOP_COUNT + if uint64(ctx.breakCount) > ctx.regValue { + ctx.breakCount = uint8(ctx.regValue) + } + case argtype.OP_SET_BREAK_COUNT_POINTER_VALUE: + ctx.breakCount = common.MAX_LOOP_COUNT + if uint64(ctx.breakCount) > ctx.pointerValue { + ctx.breakCount = uint8(ctx.pointerValue) + } + case argtype.OP_SAVE_ADDR: + ctx.saveValue(ctx.readAddr) + case argtype.OP_ADD_REG: + ctx.readAddr += ctx.regValue + case argtype.OP_SUB_REG: + ctx.readAddr -= ctx.regValue + case argtype.OP_READ_REG: + if op.PreCode == argtype.OP_SET_REG_INDEX { + ctx.regIndex = uint8(op.Value) + } + ctx.regValue = this.regValue(uint32(ctx.regIndex)) + case argtype.OP_SAVE_REG: + ctx.saveValue(ctx.regValue) + case argtype.OP_READ_POINTER: + addr := ctx.readAddr + if op.PreCode == argtype.OP_ADD_OFFSET { + addr += op.Value + } else if op.PreCode == argtype.OP_SUB_OFFSET { + addr -= op.Value + } + ctx.pointerValue = this.readPointerWidth(addr, common.NativePointerSize) + case argtype.OP_READ_POINTER32: + addr := ctx.readAddr + if op.PreCode == argtype.OP_ADD_OFFSET { + addr += op.Value + } else if op.PreCode == argtype.OP_SUB_OFFSET { + addr -= op.Value + } + ctx.pointerValue = this.readPointerWidth(addr, 4) + case argtype.OP_READ_POINTER64: + addr := ctx.readAddr + if op.PreCode == argtype.OP_ADD_OFFSET { + addr += op.Value + } else if op.PreCode == argtype.OP_SUB_OFFSET { + addr -= op.Value + } + ctx.pointerValue = this.readPointerWidth(addr, 8) + case argtype.OP_SAVE_POINTER: + ctx.saveValue(ctx.pointerValue) + case argtype.OP_SAVE_STRUCT: + ctx.readAddr = fixUserAddr(ctx.readAddr) + if op.PreCode == argtype.OP_SET_READ_COUNT { + ctx.readLen *= uint32(op.Value) + } + if ctx.readLen > common.MAX_BUF_READ_SIZE { + ctx.readLen = common.MAX_BUF_READ_SIZE + } + payload, err := this.readMemory(ctx.readAddr, ctx.readLen) + if err != nil { + payload = nil + } + ctx.saveBytes(payload, uint32(len(payload))) + case argtype.OP_SAVE_STRING: + ctx.readAddr = fixUserAddr(ctx.readAddr) + payload, err := this.readString(ctx.readAddr) + if err != nil { + payload = nil + } + ctx.saveBytes(payload, uint32(len(payload))) + case argtype.OP_SAVE_PTR_STRING: + ptr := this.readPointer(ctx.readAddr) + ctx.saveValue(ptr) + payload, err := this.readString(fixUserAddr(ptr)) + if ptr == 0 || err != nil { + ctx.saveBytes(nil, common.STRARR_MAGIC_LEN) + ctx.loopCount = ctx.breakCount + } else { + ctx.saveBytes(payload, uint32(len(payload))) + } + case argtype.OP_SAVE_STRING16: + ctx.readAddr = fixUserAddr(ctx.readAddr) + payload, err := this.readMemory(ctx.readAddr, common.MAX_BUF_READ_SIZE) + if err != nil { + payload = nil + } + ctx.saveBytes(payload, uint32(len(payload))) + case argtype.OP_SAVE_PTR_STRING16: + ptr := this.readPointer(ctx.readAddr) + ctx.saveValue(ptr) + payload, err := this.readMemory(fixUserAddr(ptr), common.MAX_BUF_READ_SIZE) + if ptr == 0 || err != nil { + ctx.saveBytes(nil, common.STRARR_MAGIC_LEN) + ctx.loopCount = ctx.breakCount + } else { + ctx.saveBytes(payload, uint32(len(payload))) + } + case argtype.OP_READ_STD_STRING: + ptr := fixUserAddr(ctx.readAddr) + value := this.readByte(ptr) + if value&1 == 0 { + ptr += 1 + } else { + ptr += 16 + ptr = this.readPointer(ptr) + } + ctx.readAddr = ptr + case argtype.OP_READ_IL2CPP_STRING: + ctx.readAddr += 0x14 + case argtype.OP_FILTER_VALUE, argtype.OP_FILTER_BUFFER, argtype.OP_FILTER_STRING: + // Brk arg capture runs after perf sample delivery, so filters are not + // applied here. Keep the op as a no-op to preserve read formatting. + default: + } +} + +func (ctx *brkOpCtx) saveValue(value uint64) { + ctx.saved.WriteByte(ctx.saveIndex) + _ = binary.Write(&ctx.saved, binary.LittleEndian, value) + ctx.saveIndex++ +} + +func (ctx *brkOpCtx) saveBytes(payload []byte, savedLen uint32) { + ctx.saved.WriteByte(ctx.saveIndex) + _ = binary.Write(&ctx.saved, binary.LittleEndian, savedLen) + if len(payload) > 0 { + ctx.saved.Write(payload) + } + ctx.saveIndex++ +} + +func (this *brkArgReader) regValue(regIndex uint32) uint64 { + var regs []uint64 + if this.event.rec.ExtraOptions.UnwindStack { + regs = this.event.UnwindBuffer.Regs + } else { + regs = this.event.RegsBuffer.Regs + } + if int(regIndex) >= len(regs) { + return 0 + } + return regs[regIndex] +} + +func (this *brkArgReader) readPointer(addr uint64) uint64 { + return this.readPointerWidth(addr, common.NativePointerSize) +} + +func (this *brkArgReader) readPointerWidth(addr uint64, ptrSize uint32) uint64 { + payload, err := this.readMemory(fixUserAddr(addr), ptrSize) + if err != nil || uint32(len(payload)) != ptrSize { + return 0 + } + if ptrSize == 4 { + return uint64(binary.LittleEndian.Uint32(payload)) + } + return binary.LittleEndian.Uint64(payload) +} + +func (this *brkArgReader) readByte(addr uint64) byte { + payload, err := this.readMemory(addr, 1) + if err != nil || len(payload) == 0 { + return 0 + } + return payload[0] +} + +func (this *brkArgReader) readString(addr uint64) ([]byte, error) { + addr = fixUserAddr(addr) + if addr == 0 { + return nil, fmt.Errorf("zero address") + } + if err := this.openMem(); err != nil { + return nil, err + } + + var payload []byte + const chunkSize = 256 + for len(payload) < maxStringReadSize { + remain := maxStringReadSize - len(payload) + if remain > chunkSize { + remain = chunkSize + } + chunk := make([]byte, remain) + n, err := this.mem.ReadAt(chunk, int64(addr)+int64(len(payload))) + if n > 0 { + payload = append(payload, chunk[:n]...) + if idx := bytes.IndexByte(chunk[:n], 0); idx >= 0 { + return payload[:len(payload)-n+idx+1], nil + } + } + if err != nil { + if len(payload) > 0 { + return payload, nil + } + return nil, err + } + } + return payload, nil +} + +func (this *brkArgReader) readMemory(addr uint64, size uint32) ([]byte, error) { + if size == 0 { + return []byte{}, nil + } + if addr == 0 { + return nil, fmt.Errorf("zero address") + } + if err := this.openMem(); err != nil { + return nil, err + } + payload := make([]byte, size) + n, err := this.mem.ReadAt(payload, int64(addr)) + if err != nil && err != io.EOF { + if n > 0 { + return payload[:n], err + } + return nil, err + } + return payload[:n], err +} + +func (this *brkArgReader) openMem() error { + if this.mem != nil { + return nil + } + mem, err := os.Open(fmt.Sprintf("/proc/%d/mem", this.pid)) + if err != nil { + return err + } + this.mem = mem + return nil +} + +func fixUserAddr(addr uint64) uint64 { + return addr & 0xffffffffffff +} diff --git a/user/event_parser/parser.go b/user/event_parser/parser.go index c3e1930c..1eaa2bac 100644 --- a/user/event_parser/parser.go +++ b/user/event_parser/parser.go @@ -75,7 +75,7 @@ func (this *EventParser) ParseDump(dump_name string) { rec.ExtraOptions = &perf.ExtraPerfOptions{ UnwindStack: this.mconf.UnwindStack, - ShowRegs: this.mconf.ShowRegs, + ShowRegs: this.mconf.ShowRegs || (this.mconf.BrkPointConf != nil && this.mconf.BrkPointConf.IsEnable()), BrkAddr: this.mconf.BrkAddr, BrkLen: this.mconf.BrkLen, BrkType: this.mconf.BrkType, diff --git a/user/module/imodule.go b/user/module/imodule.go index 00a2d6e1..62558463 100644 --- a/user/module/imodule.go +++ b/user/module/imodule.go @@ -197,6 +197,9 @@ func (this *Module) getExtraOptions(em *ebpf.Map) perf.ExtraPerfOptions { } else { ShowRegs = this.mconf.ShowRegs } + if this.mconf.BrkPointConf != nil && this.mconf.BrkPointConf.IsEnable() { + ShowRegs = true + } BrkPid := this.mconf.BrkPid // 对内核地址断点的时候无法指定pid为用户进程的pid if this.mconf.BrkKernel { diff --git a/user/rpc/rpc.go b/user/rpc/rpc.go index fd80d82c..2aa59a03 100644 --- a/user/rpc/rpc.go +++ b/user/rpc/rpc.go @@ -62,11 +62,19 @@ func BrkIt(opts *BrkOptions) { mconfig.StackSize = Gconfig.StackSize mconfig.ShowRegs = Gconfig.ShowRegs mconfig.GetOff = Gconfig.GetOff + mconfig.Is32Bit = Gconfig.Is32Bit() mconfig.BrkPid = opts.BrkPid mconfig.BrkAddr = opts.BrkAddr mconfig.BrkLen = opts.BrkLen mconfig.BrkType = opts.BrkType mconfig.BrkKernel = false + mconfig.BrkPointConf = &config.BrkPointConfig{} + mconfig.BrkPointConf.SetDumpHex(Gconfig.DumpHex) + mconfig.BrkPointConf.SetDumpBase64(Gconfig.DumpBase64) + mconfig.BrkPointConf.SetColor(Gconfig.Color) + if err := mconfig.BrkPointConf.Parse_BrkPoint(Gconfig.BrkPoint); err != nil { + panic(err) + } mod.Init(Ctx, Logger, mconfig) err := mod.Run() if err != nil {