package ebpftracer import ( "bufio" "bytes" "debug/buildinfo" "debug/elf" "errors" "fmt" "os" "regexp" "strconv" "strings" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" "github.com/coroot/coroot-node-agent/ebpftracer/tracer" "github.com/coroot/coroot-node-agent/proc" . "github.com/coroot/coroot-node-agent/utils/modelse" klog "github.com/sirupsen/logrus" "golang.org/x/arch/arm64/arm64asm" "golang.org/x/arch/x86/x86asm" "golang.org/x/mod/semver" ) const ( minSupportedGoVersion = "v1.15.0" goTlsWriteSymbol = "crypto/tls.(*Conn).Write" goTlsReadSymbol = "crypto/tls.(*Conn).Read" goExecute = "runtime.execute" goNewproc1 = "runtime.newproc1" goRunqget = "runtime.runqget" goGoready = "runtime.goready" goServeHTTP = "net/http.serverHandler.ServeHTTP" goTransport = "net/http.(*Transport).roundTrip" goHeaderWriteSubset = "net/http.Header.writeSubset" goGrpcServerHandleStream = "google.golang.org/grpc.(*Server).handleStream" goGrpcHttp2OperateHeader = "google.golang.org/grpc/internal/transport.(*http2Server).operateHeaders" goGrpcServerWritestatus = "google.golang.org/grpc/internal/transport.(*http2Server).WriteStatus" goGrpcClientConnInvoke = "google.golang.org/grpc.(*ClientConn).Invoke" goGrpcClientLoopyHeaderHandler = "google.golang.org/grpc/internal/transport.(*loopyWriter).headerHandler" goGrpcHttp2ClientNewStream = "google.golang.org/grpc/internal/transport.(*http2Client).NewStream" goReadContinuedLineSlice = "net/textproto.(*Reader).readContinuedLineSlice" goGocqlSessionExecuteQuery = "github.com/gocql/gocql.(*Session).executeQuery" goGocqlSessionExecuteQueryV2 = "github.com/apache/cassandra-gocql-driver/v2.(*Session).executeQuery" goKafkaWriterWriteMessages = "github.com/segmentio/kafka-go.(*Writer).WriteMessages" goKafkaReaderFetchMessage = "github.com/segmentio/kafka-go.(*Reader).FetchMessage" ) var ( opensslVersionRe = regexp.MustCompile(`OpenSSL\s(\d\.\d+\.\d+)`) ) func (t *Tracer) AttachOpenSslUprobes(pid uint32) ([]link.Link, error) { if t.DisableL7Tracing() { return nil, nil } libPath, version := getSslLibPathAndVersion(pid) if libPath == "" || version == "" { return nil, nil } log := func(msg string, err error) { if err != nil { for _, s := range []string{"no such file or directory", "no such process", "permission denied"} { if strings.HasSuffix(err.Error(), s) { return } } klog.Errorf("pid=%d libssl_version=%s: %s: %s", pid, version, msg, err) return } klog.Infof("pid=%d libssl_version=%s: %s", pid, version, msg) } exe, err := link.OpenExecutable(libPath) if err != nil { log("failed to open executable", err) return nil, err } var links []link.Link writeEnter := "openssl_SSL_write_enter" readEnter := "openssl_SSL_read_enter" readExEnter := "openssl_SSL_read_ex_enter" readExit := "openssl_SSL_read_exit" switch { case semver.Compare(version, "v3.0.0") >= 0: writeEnter = "openssl_SSL_write_enter_v3_0" readEnter = "openssl_SSL_read_enter_v3_0" readExEnter = "openssl_SSL_read_ex_enter_v3_0" case semver.Compare(version, "v1.1.1") >= 0: writeEnter = "openssl_SSL_write_enter_v1_1_1" readEnter = "openssl_SSL_read_enter_v1_1_1" readExEnter = "openssl_SSL_read_ex_enter_v1_1_1" } type prog struct { symbol string uprobe string uretprobe string } progs := []prog{ {symbol: "SSL_write", uprobe: writeEnter}, {symbol: "SSL_read", uprobe: readEnter}, {symbol: "SSL_read", uretprobe: readExit}, } if semver.Compare(version, "v1.1.1") >= 0 { progs = append(progs, []prog{ {symbol: "SSL_write_ex", uprobe: writeEnter}, {symbol: "SSL_read_ex", uprobe: readExEnter}, {symbol: "SSL_read_ex", uretprobe: readExit}, }...) } for _, p := range progs { if p.uprobe != "" { l, err := exe.Uprobe(p.symbol, t.uprobes[p.uprobe], nil) klog.Infoln("fucktls crypto/tls uprobes attached", p.symbol) if err != nil { //log("failed to attach uprobe", err) klog.Infoln("fucktls crypto/tls uprobes attached error", p.symbol) return nil, err } links = append(links, l) } if p.uretprobe != "" { klog.Infoln("fucktls crypto/tls uprobes attached ret", p.symbol) l, err := exe.Uretprobe(p.symbol, t.uprobes[p.uretprobe], nil) if err != nil { klog.Infoln("fucktls crypto/tls uprobes attached ret error", p.symbol) //log("failed to attach uretprobe", err) return nil, err } links = append(links, l) } } //log("libssl uprobes attached", nil) return links, nil } func (t *Tracer) AttachGoTlsUprobes(pid uint32, appInfo *AppInfo, codeType uint16) ([]link.Link, error) { if t.DisableL7Tracing() { return nil, nil } path := proc.Path(pid, "exe") instanceID := appInfo.InstanceIdHash.HashtVal appID := appInfo.AppIdHash.HashtVal var err error var name, version string var major, minor, revision int log := func(msg string, err error) { if err != nil { for _, s := range []string{"not a Go executable", "no such file or directory", "no such process", "permission denied"} { if strings.HasSuffix(err.Error(), s) { return } } klog.Errorf("pid=%d golang_app=%s golang_version=%s: %s: %s", pid, name, version, msg, err) return } klog.Infof("pid=%d golang_app=%s golang_version=%s: %s", pid, name, version, msg) } bi, err := buildinfo.ReadFile(path) if err != nil { log("failed to read build info", err) return nil, err } // isGolangApp = true name, err = os.Readlink(path) if err != nil { log("failed to read name", err) return nil, err } version = strings.Replace(bi.GoVersion, "go", "v", 1) if semver.Compare(version, minSupportedGoVersion) < 0 { log(fmt.Sprintf("go_versions below %s are not supported", minSupportedGoVersion), nil) return nil, err } appInfo.Version = version ef, err := elf.Open(path) if err != nil { log("failed to open as elf binary", err) return nil, err } defer ef.Close() symbols, err := ef.Symbols() if err != nil { if errors.Is(err, elf.ErrNoSymbols) { log("no symbol section", err) return nil, err } log("failed to read symbols", err) return nil, err } textSection := ef.Section(".text") if textSection == nil { log("no text section", err) return nil, err } textSectionData, err := textSection.Data() if err != nil { log("failed to read text section", err) return nil, err } textSectionLen := uint64(len(textSectionData) - 1) exe, err := link.OpenExecutable(path) if err != nil { log("failed to open executable", err) return nil, err } // 检测 gRPC 版本 var grpcMajorVersion, grpcMinorVersion int for _, dep := range bi.Deps { if strings.Contains(dep.Path, "grpc") { klog.Infoln("Found gRPC dependency:", dep.Path, "version:", dep.Version) // 解析版本号 version := dep.Version if version != "" { // 移除可能的 "v" 前缀 version = strings.TrimPrefix(version, "v") parts := strings.Split(version, ".") if len(parts) >= 2 { major, err := strconv.Atoi(parts[0]) if err != nil { klog.WithError(err).Warnf("Error parsing major version from %s", parts[0]) continue } minor, err := strconv.Atoi(parts[1]) if err != nil { klog.WithError(err).Warnf("Error parsing minor version from %s", parts[1]) continue } klog.Infof("Detected gRPC version: %d.%d for PID %d", major, minor, pid) grpcMajorVersion = major grpcMinorVersion = minor // // 根据版本选择相应的探针策略 // if major == 1 && minor >= 69 { // klog.Infof("Using modern gRPC handler for version %d.%d", major, minor) // } else { // klog.Infof("Using legacy gRPC handler for version %d.%d", major, minor) // } } } } } offset, ok := tracer.GetOffset(tracer.NewID("std", "runtime", "g", "goid"), path) if ok { klog.Infof("[AttachGoTlsUprobes] STEP 11.1: Successfully got goid offset=%d", offset) } else { klog.Errorf("[AttachGoTlsUprobes] STEP 11.2: Failed to get goid offset, pid=%d, version=%s", pid, bi.GoVersion) } // 获取 runtime.p.goidcache 偏移量 klog.Infof("[AttachGoTlsUprobes] STEP 11.3: Getting offset for runtime.p.goidcache") goidcacheOffset, okGoidcache := tracer.GetOffset(tracer.NewID("std", "runtime", "p", "goidcache"), path) if okGoidcache { klog.Infof("[AttachGoTlsUprobes] STEP 11.4: Successfully got goidcache offset=%d", goidcacheOffset) } else { klog.Warnf("[AttachGoTlsUprobes] STEP 11.5: Failed to get goidcache offset, pid=%d, version=%s, using fallback", pid, bi.GoVersion) goidcacheOffset = 384 // fallback value } // 获取 runtime.p.runnext 偏移量 klog.Infof("[AttachGoTlsUprobes] STEP 11.6: Getting offset for runtime.p.runnext") runnextOffset, okRunnext := tracer.GetOffset(tracer.NewID("std", "runtime", "p", "runnext"), path) if okRunnext { klog.Infof("[AttachGoTlsUprobes] STEP 11.7: Successfully got runnext offset=%d", runnextOffset) } else { klog.Warnf("[AttachGoTlsUprobes] STEP 11.8: Failed to get runnext offset, pid=%d, version=%s, using fallback", pid, bi.GoVersion) runnextOffset = 2456 // fallback value } klog.Infof("[AttachGoTlsUprobes] STEP 12: Getting offset for runtime.hmap.buckets") bucketsOff, ok2 := tracer.GetOffset(tracer.NewID("std", "runtime", "hmap", "buckets"), path) // Go 1.24+ 使用新的 map 实现(Swiss Tables),使用 internal/runtime/maps.Map 而不是 runtime.hmap if !ok2 { klog.Errorf("[AttachGoTlsUprobes] STEP 12.2: Failed to get buckets offset, pid=%d, version=%s", pid, bi.GoVersion) klog.Infof("[AttachGoTlsUprobes] STEP 12.3: Trying Swiss Tables maps.Map.dirPtr for Go 1.24+") // Go 1.24+ Swiss Tables 使用 internal/runtime/maps.Map 结构体 // 结构体字段:used uint64, seed uintptr, dirPtr unsafe.Pointer (相当于旧的 buckets) // 尝试获取 maps.Map.dirPtr 的偏移量 // 注意:DWARF 中的包路径可能是 "internal/runtime/maps" 或 "internal/runtime/maps.Map" swissFields := []struct { pkg string structName string field string }{ {"internal/runtime/maps", "Map", "dirPtr"}, {"internal.runtime.maps", "Map", "dirPtr"}, {"maps", "Map", "dirPtr"}, } for _, sf := range swissFields { // 尝试不同的包路径格式 swissOff, swissOk := tracer.GetOffset(tracer.NewID("std", sf.pkg, sf.structName, sf.field), path) if swissOk { klog.Infof("[AttachGoTlsUprobes] STEP 12.4: Found Swiss Tables field '%s.%s.%s' with offset=%d", sf.pkg, sf.structName, sf.field, swissOff) bucketsOff = swissOff ok2 = true break } else { klog.Debugf("[AttachGoTlsUprobes] STEP 12.4: Trying Swiss Tables field '%s.%s.%s' not found", sf.pkg, sf.structName, sf.field) } } // 如果还是找不到,尝试旧的 hmap 字段作为备选 if !ok2 { klog.Infof("[AttachGoTlsUprobes] STEP 12.5: Trying alternative hmap field names") alternativeFields := []string{"table", "swiss", "swissTable", "buckets1", "oldbuckets", "bmap", "extra"} for _, fieldName := range alternativeFields { altOff, altOk := tracer.GetOffset(tracer.NewID("std", "runtime", "hmap", fieldName), path) if altOk { klog.Infof("[AttachGoTlsUprobes] STEP 12.6: Found alternative field '%s' with offset=%d", fieldName, altOff) bucketsOff = altOff ok2 = true break } } } if !ok2 { klog.Errorf("[AttachGoTlsUprobes] STEP 12.7: All attempts failed, Go 1.24+ Swiss Tables map structure not found") // 根据源码分析,maps.Map 结构体布局(64-bit): // - used uint64 (8 bytes, offset 0) // - seed uintptr (8 bytes, offset 8) // - dirPtr unsafe.Pointer (8 bytes, offset 16) <- 相当于旧的 buckets // 如果 DWARF 查找失败,使用硬编码的偏移量作为 fallback // 注意:这需要确认目标系统是 64-bit,且结构体对齐正确 klog.Warnf("[AttachGoTlsUprobes] STEP 12.8: Using hardcoded offset for maps.Map.dirPtr (offset=16 on 64-bit)") klog.Warnf("[AttachGoTlsUprobes] STEP 12.9: This assumes: used(uint64@0) + seed(uintptr@8) + dirPtr(unsafe.Pointer@16)") // 检查 Go 版本是否 >= 1.24 realVersion := strings.Replace(bi.GoVersion, "go", "", 1) parts := strings.Split(realVersion, ".") if len(parts) >= 2 { major, _ = strconv.Atoi(parts[0]) minor, _ = strconv.Atoi(parts[1]) if major > 1 || (major == 1 && minor >= 24) { // Go 1.24+ 使用 Swiss Tables,maps.Map.dirPtr 在 offset 16 (64-bit) // 假设是 64-bit 系统(大多数生产环境) bucketsOff = 16 ok2 = true klog.Infof("[AttachGoTlsUprobes] STEP 12.10: Using hardcoded offset=%d for Go %s (Swiss Tables)", bucketsOff, bi.GoVersion) } else { klog.Errorf("[AttachGoTlsUprobes] STEP 12.11: Go version < 1.24 but buckets not found, this is unexpected") bucketsOff = 0 } } else { klog.Errorf("[AttachGoTlsUprobes] STEP 12.12: Failed to parse Go version: %s", bi.GoVersion) bucketsOff = 0 } } } else { klog.Infof("[AttachGoTlsUprobes] STEP 12.1: Successfully got buckets offset=%d", bucketsOff) } klog.Infof("[AttachGoTlsUprobes] STEP 13: Checking if both offsets are valid, goid_ok=%v, buckets_ok=%v", ok, ok2) // Go 1.24+ 兼容:如果 goid 成功但 buckets 失败,仍然继续(但记录警告) if ok { if !ok2 { klog.Warnf("[AttachGoTlsUprobes] STEP 13.0: buckets offset missing for Go 1.24+, but continuing with goid only") // 对于 Go 1.24,可能需要调整后续逻辑,暂时允许继续 } klog.Infof("[AttachGoTlsUprobes] STEP 13.1: Both offsets valid, proceeding with version encoding") klog.Infof("[AttachGoTlsUprobes] STEP 14: Parsing Go version string") realVersion := strings.Replace(bi.GoVersion, "go", "", 1) klog.Infof("[AttachGoTlsUprobes] STEP 14.1: Real version string=%s", realVersion) parts := strings.Split(realVersion, ".") if len(parts) >= 2 { major, err = strconv.Atoi(parts[0]) if err != nil { log("Error converting major version:", err) return nil, err } minor, err = strconv.Atoi(parts[1]) if err != nil { log("Error converting minor version:", err) return nil, err } if len(parts) >= 3 { revision, err = strconv.Atoi(parts[2]) if err != nil { log("Error converting revision version:", err) } } klog.Infof("[AttachGoTlsUprobes] Parsed version, major=%d, minor=%d, revision=%d", major, minor, revision) goVersion := ((major & 0xFF) << 16) + ((minor & 0xFF) << 8) + min(revision, 255) klog.Infof("[AttachGoTlsUprobes] Initializing EbpfProcInfo structure") info := EbpfProcInfo{} info.Version = uint32(goVersion) info.Offsets[OFFSET_IDX_GOID_RUNTIME_G] = uint16(offset) info.Offsets[OFFSET_IDX_P_GOIDCACHE] = uint16(goidcacheOffset) info.Offsets[OFFSET_IDX_P_RUNNEXT] = uint16(runnextOffset) info.NetTCPConnItab = uint64(0) info.CryptoTLSConnItab = uint64(0) info.CredentialsSyscallConnItab = uint64(0) info.InstanceId = instanceID info.AppId = appID info.CodeType = codeType if major == 1 && minor >= 24 { info.UseSwissMap = uint64(1) } if grpcMajorVersion >= 1 && grpcMinorVersion >= 60 { info.IsNewFramePos = 1 klog.Infof("[AttachGoTlsUprobes] Using new frame position for gRPC >= 1.60") } else { info.IsNewFramePos = 0 klog.Infof("[AttachGoTlsUprobes] Using old frame position for gRPC < 1.60") } // go info.BucketsPtrPos = bucketsOff if bucketsOff == 0 { klog.Warnf("[AttachGoTlsUprobes] STEP 15.3: BucketsPtrPos=0 (Go 1.24+ may not use buckets field)") } else { klog.Infof("[AttachGoTlsUprobes] STEP 15.3: Basic info initialized, BucketsPtrPos=%d", bucketsOff) } klog.Infof("[AttachGoTlsUprobes] STEP 16: Getting offsets for HTTP, gRPC and Kafka fields") fields := map[*uint64]tracer.ID{ &info.MethodPtrPos: tracer.NewID("std", "net/http", "Request", "Method"), &info.UrlPtrPos: tracer.NewID("std", "net/http", "Request", "URL"), &info.PathPtrPos: tracer.NewID("std", "net/url", "URL", "Path"), &info.StatusCodePos: tracer.NewID("std", "net/http", "response", "status"), &info.RequestHostPos: tracer.NewID("std", "net/http", "Request", "Host"), &info.ProtoPos: tracer.NewID("std", "net/http", "Request", "Proto"), &info.CtxPtrPos: tracer.NewID("std", "net/http", "Request", "ctx"), &info.HeadersPtrPos: tracer.NewID("std", "net/http", "Request", "Header"), &info.HttpClientNextidPos: tracer.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "http2Client", "nextID"), &info.StreamMethodPtrPos: tracer.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "Stream", "method"), &info.StreamCtxPos: tracer.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "Stream", "ctx"), &info.IoWriterBufPtrPos: tracer.NewID("std", "bufio", "Writer", "buf"), &info.IoWriterNPos: tracer.NewID("std", "bufio", "Writer", "n"), // Kafka Message fields &info.KafkaMessageKeyPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Key"), &info.KafkaMessageTopicPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Topic"), &info.KafkaMessageHeadersPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Headers"), &info.KafkaMessageTimePos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Time"), &info.KafkaMessagePartitionPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Partition"), &info.KafkaMessageOffsetPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Offset"), &info.KafkaMessageValuePos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Value"), // Kafka Writer fields &info.KafkaWriterTopicPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Writer", "Topic"), // Try both Brokers and Addr fields - Addr is a string, Brokers is []string &info.KafkaWriterAddrPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Writer", "Addr"), // Kafka Reader fields // Note: Reader.config is unexported, try both "config" and "Config" &info.KafkaReaderConfigPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Reader", "config"), &info.KafkaReaderConfigGroupIDPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "ReaderConfig", "GroupID"), &info.KafkaReaderConfigTopicsPos: tracer.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "ReaderConfig", "topics"), // net.TCPAddr offsets (standard library) &info.TcpAddrIPOffset: tracer.NewID("std", "net", "TCPAddr", "IP"), &info.TcpAddrPortOffset: tracer.NewID("std", "net", "TCPAddr", "Port"), } successCount := 0 failCount := 0 for field, id := range fields { off, ok := tracer.GetOffset(id, path) if !ok { klog.Warnf("[AttachGoTlsUprobes] STEP 16.1: Failed to get offset for ID: %v (PkgPath=%s, Struct=%s, Field=%s)", id, id.PkgPath, id.Struct, id.Field) failCount++ } else { successCount++ klog.Debugf("[AttachGoTlsUprobes] STEP 16.2: Got offset for %s.%s.%s = %d", id.PkgPath, id.Struct, id.Field, off) } *field = off } klog.Infof("[AttachGoTlsUprobes] Field offset collection completed, success=%d, failed=%d", successCount, failCount) // 获取内存地址 if appInfo.GoProcCache.StartAddr == 0 && appInfo.GoProcCache.EndAddr == 0 { klog.Infof("[AttachGoTlsUprobes] Cache empty, calling Allocate") allocDetails, allocErr := tracer.Allocate(int(pid)) if allocErr != nil { return nil, allocErr } if allocDetails != nil { appInfo.GoProcCache.StartAddr = allocDetails.StartAddr appInfo.GoProcCache.EndAddr = allocDetails.EndAddr klog.Infof("[AttachGoTlsUprobes] Allocate succeeded, StartAddr=0x%x, EndAddr=0x%x", allocDetails.StartAddr, allocDetails.EndAddr) } else { klog.Warnf("[AttachGoTlsUprobes] Allocate returned nil") } } else { klog.Infof("[AttachGoTlsUprobes] Using cached addresses, StartAddr=0x%x, EndAddr=0x%x", appInfo.GoProcCache.StartAddr, appInfo.GoProcCache.EndAddr) } info.StartAddr = appInfo.GoProcCache.StartAddr info.EndAddr = appInfo.GoProcCache.EndAddr klog.Debugln("Major:", major) klog.Debugln("Minor:", minor) klog.Debugln("Revision:", revision) klog.Debugln("goVersion", goVersion) klog.WithField("pid", pid).Debugln("info.StartAddr", info.StartAddr) klog.WithField("pid", pid).Debugln("info.EndAddr", info.EndAddr) klog.Infof("[AttachGoTlsUprobes] Updating proc_info map") _, err = tracer.UpdateProcInfoToMap(t.collection, pid, info) if err != nil { klog.Error("failed to update program info", err) return nil, err } klog.Infof("[AttachGoTlsUprobes] Proc_info map updated successfully") appInfo.EBPFProcInfo = &info } else { klog.Errorf("[AttachGoTlsUprobes] Skipping proc_info initialization due to missing offsets, goid_ok=%v, buckets_ok=%v", ok, ok2) if !ok { klog.Errorf("[AttachGoTlsUprobes] runtime.g.goid offset missing - this is critical!") } if !ok2 { klog.Errorf("[AttachGoTlsUprobes] runtime.hmap.buckets offset missing - Go 1.24+ may use new map implementation") } } } klog.Infof("[AttachGoTlsUprobes] Starting symbol matching and uprobe attachment, total symbols=%d", len(symbols)) var links []link.Link matchedSymbols := 0 for i, s := range symbols { if elf.ST_TYPE(s.Info) != elf.STT_FUNC || s.Size == 0 { continue } switch s.Name { case goTlsWriteSymbol, goTlsReadSymbol: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.1: Matched TLS symbol: %s (index=%d)", s.Name, i) case goExecute: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.2: Matched runtime.execute symbol (index=%d)", i) case goNewproc1: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.3: Matched runtime.newproc1 symbol (index=%d)", i) case goRunqget: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.4: Matched runtime.runqget symbol (index=%d)", i) case goServeHTTP: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.6: Matched net/http.serverHandler.ServeHTTP symbol (index=%d)", i) case goTransport, goHeaderWriteSubset: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.7: Matched net/http.Transport.roundTrip writeSubset symbol (index=%d)", i) case goGrpcClientConnInvoke: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.8: Matched gRPC ClientConn.Invoke symbol (index=%d)", i) case goGrpcHttp2OperateHeader, goGrpcServerHandleStream, goGrpcServerWritestatus, goGrpcClientLoopyHeaderHandler, goGrpcHttp2ClientNewStream: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.9: Matched gRPC symbol: %s (index=%d)", s.Name, i) case goGocqlSessionExecuteQuery: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.10: Matched gocql Session.executeQuery symbol (index=%d)", i) case goGocqlSessionExecuteQueryV2: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.11: Matched cassandra Session.executeQuery symbol (index=%d)", i) case goKafkaWriterWriteMessages: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.13: Matched kafka Writer.WriteMessages symbol (index=%d)", i) case goKafkaReaderFetchMessage: matchedSymbols++ klog.Infof("[AttachGoTlsUprobes] STEP 19.14: Matched kafka Reader.FetchMessage symbol (index=%d)", i) case goReadContinuedLineSlice: default: continue } klog.Debugf("[AttachGoTlsUprobes] Processing symbol %s, Value=0x%x, Size=%d", s.Name, s.Value, s.Size) address := s.Value for _, p := range ef.Progs { if p.Type != elf.PT_LOAD || (p.Flags&elf.PF_X) == 0 { continue } if p.Vaddr <= s.Value && s.Value < (p.Vaddr+p.Memsz) { address = s.Value - p.Vaddr + p.Off break } } //fmt.Println("s.Name-----:", s.Name) switch s.Name { case goExecute: klog.Infof("[AttachGoTlsUprobes] STEP 20: Attaching uprobe for runtime.execute, address=0x%x", address) l, err := attachUprobe(exe, s.Name, "runtime_execute", t.uprobes, address, "failed to attach write_enter uprobe", true) if err != nil { klog.Infoln("runtime.execute no") return nil, err } klog.Infof("[AttachGoTlsUprobes] STEP 20.2: Successfully attached runtime.execute uprobe") klog.Infoln("runtime.execute ok") links = append(links, l) case goNewproc1: klog.Infof("[AttachGoTlsUprobes] STEP 21: Attaching uprobe for runtime.newproc1, address=0x%x", address) retLinks, err := attachUprobeWithReturns(exe, s.Name, "enter_runtime_newproc1", "exit_runtime_newproc1", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach newproc1 uprobe", true) if err != nil { log("failed to attach newproc1 uprobe", err) return nil, err } links = append(links, retLinks...) klog.Infof("[AttachGoTlsUprobes] STEP 21.2: Successfully attached enter_runtime_newproc1 uprobe") case goRunqget: l, err := attachUprobe(exe, s.Name, "enter_runtime_runqget", t.uprobes, address, "failed to attach goRunqget uprobe", true) if err != nil { log("failed to attach goRunqget uprobe", err) return nil, err } links = append(links, l) //case goGoready: //klog.Infof("[AttachGoTlsUprobes] STEP 22: Attaching uprobe for runtime.goready, address=0x%x", address) //l, err := attachUprobe(exe, s.Name, "runtime_goready", t.uprobes, address, "failed to attach runtime.goready uprobe", true) //if err != nil { // klog.Infoln("runtime.goready no") // return nil, err //} //klog.Infof("[AttachGoTlsUprobes] STEP 22.2: Successfully attached runtime.goready uprobe") //klog.Infoln("runtime.goready ok") //links = append(links, l) case goGrpcClientConnInvoke: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_ClientConn_Invoke", "uprobe_ClientConn_Invoke_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_ClientConn_Invoke uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("uprobe_ClientConn_Invoke ok") links = append(links, retLinks...) } case goGrpcClientLoopyHeaderHandler: l, err := attachUprobe(exe, s.Name, "uprobe_LoopyWriter_HeaderHandler", t.uprobes, address, "failed to attach uprobe_LoopyWriter_HeaderHandler uprobe", false) if err == nil && l != nil { klog.Infoln("uprobe_LoopyWriter_HeaderHandler ok") links = append(links, l) } case goGrpcHttp2ClientNewStream: l, err := attachUprobe(exe, s.Name, "uprobe_http2Client_NewStream", t.uprobes, address, "failed to attach uprobe_http2Client_NewStream uprobe", false) if err == nil && l != nil { klog.Infoln("uprobe_http2Client_NewStream ok") links = append(links, l) } case goGrpcHttp2OperateHeader: l, err := attachUprobe(exe, s.Name, "uprobe_http2Server_operateHeader", t.uprobes, address, "failed to attach uprobe_http2Server_operateHeader uprobe", false) if err == nil && l != nil { klog.Infoln("uprobe_http2Server_operateHeader ok") links = append(links, l) } // case goGrpcServerWritestatus: // // 根据 gRPC 版本选择相应的 WriteStatus 探针 // l, err := exe.Uprobe(s.Name, t.uprobes["uprobe_http2Server_WriteStatus"], &link.UprobeOptions{Address: address}) // if err != nil { // klog.WithError(err).Errorf("failed to attach uprobe_http2Server_WriteStatus uprobe") // continue // } // links = append(links, l) case goGrpcServerHandleStream: // 根据 gRPC 版本选择相应的探针 probeName := t.selectGRPCServerProbe(grpcMajorVersion, grpcMinorVersion) retLinks, err := attachUprobeWithReturns(exe, s.Name, probeName, "uprobe_server_handleStream_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, fmt.Sprintf("failed to attach %s uprobe", probeName), true) if err != nil { return nil, err } if retLinks != nil { klog.Infof("%s ok (gRPC v%d.%d)", probeName, grpcMajorVersion, grpcMinorVersion) klog.Infoln("google.golang.org/grpc.(*Server).handleStream ok----") links = append(links, retLinks...) } case goServeHTTP: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_HandlerFunc_ServeHTTP", "uprobe_HandlerFunc_ServeHTTP_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_HandlerFunc_ServeHTTP uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("net/http.serverHandler.ServeHTTP ok") links = append(links, retLinks...) } case goTransport: if t.DisableE2ETracing() { continue } retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Transport_roundTrip", "uprobe_Transport_roundTrip_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach write_enter uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("net/http.uprobe_Transport_roundTrip ok") links = append(links, retLinks...) } case goHeaderWriteSubset: if t.DisableE2ETracing() { continue } l, err := attachUprobe(exe, s.Name, "uprobe_writeSubset", t.uprobes, address, "failed to attach write_enter uprobe", true) if err != nil { klog.WithError(err).Errorln("failed to attach uprobe_writeSubset") return nil, err } klog.Infoln("net/http.Header.writeSubset ok") links = append(links, l) case goTlsWriteSymbol: klog.Infoln("fucktls goTlsWriteSymbol crypto/tls uprobes attached") l, err := attachUprobe(exe, s.Name, "go_crypto_tls_write_enter", t.uprobes, address, "failed to attach write_enter uprobe", true) if err != nil { klog.WithError(err).Errorln("failed to attach write_enter uprobe") return nil, err } links = append(links, l) case goTlsReadSymbol: klog.Infoln("fucktls goTlsReadSymbol crypto/tls uprobes attached") retLinks, err := attachUprobeWithReturns(exe, s.Name, "go_crypto_tls_read_enter", "go_crypto_tls_read_exit", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach read_enter uprobe", true) if err != nil { klog.WithError(err).Errorln("failed to attach read_enter uprobe") return nil, err } if retLinks != nil { links = append(links, retLinks...) } case goReadContinuedLineSlice: if major == 1 && minor >= 24 { retLinks, err := attachUprobeWithReturns(exe, s.Name, "", "uprobe_textproto_Reader_readContinuedLineSlice_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach read_exit uprobe", true) if err != nil { return nil, err } if retLinks != nil { links = append(links, retLinks...) } } case goGocqlSessionExecuteQuery: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Session_executeQuery", "uprobe_Session_executeQuery_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_Session_executeQuery uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("uprobe_Session_executeQuery ok") links = append(links, retLinks...) } case goGocqlSessionExecuteQueryV2: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Session_executeQuery_cassandra", "uprobe_Session_executeQuery_cassandra_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_Session_executeQuery_cassandra uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("uprobe_Session_executeQuery_cassandra ok") links = append(links, retLinks...) } case goKafkaWriterWriteMessages: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_WriteMessages", "uprobe_WriteMessages_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_WriteMessages uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("uprobe_WriteMessages ok") links = append(links, retLinks...) } case goKafkaReaderFetchMessage: retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_FetchMessage", "uprobe_FetchMessage_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_FetchMessage uprobe", true) if err != nil { return nil, err } if retLinks != nil { klog.Infoln("uprobe_FetchMessage ok") links = append(links, retLinks...) } } } klog.Infof("[AttachGoTlsUprobes] Symbol processing completed, matched symbols=%d, total links=%d", matchedSymbols, len(links)) if len(links) == 0 { klog.Errorf("[AttachGoTlsUprobes] No uprobes attached, returning error") return nil, err } klog.Infof("[AttachGoTlsUprobes] Function completed successfully, attached %d uprobes", len(links)) klog.Infoln("crypto/tls uprobes attached") return links, nil } func getSslLibPathAndVersion(pid uint32) (string, string) { f, err := os.Open(proc.Path(pid, "maps")) if err != nil { return "", "" } defer f.Close() scanner := bufio.NewScanner(f) scanner.Split(bufio.ScanLines) var libsslPath, libcryptoPath string for scanner.Scan() { parts := strings.Fields(scanner.Text()) if len(parts) <= 5 { continue } libPath := parts[5] switch { case libsslPath == "" && strings.Contains(libPath, "libssl.so"): fullPath := proc.Path(pid, "root", libPath) if _, err = os.Stat(fullPath); err == nil { libsslPath = fullPath } case libcryptoPath == "" && strings.Contains(libPath, "libcrypto.so"): fullPath := proc.Path(pid, "root", libPath) if _, err = os.Stat(fullPath); err == nil { libcryptoPath = fullPath } default: continue } if libsslPath != "" && libcryptoPath != "" { break } } if libsslPath == "" || libcryptoPath == "" { return "", "" } ef, err := elf.Open(libcryptoPath) if err != nil { return "", "" } defer ef.Close() rodataSection := ef.Section(".rodata") if rodataSection == nil { return "", "" } rodataSectionData, err := rodataSection.Data() if err != nil { return "", "" } var version string for _, b := range bytes.Split(rodataSectionData, []byte("\x00")) { if len(b) == 0 { continue } s := string(b) if !strings.HasPrefix(s, "OpenSSL") { continue } if m := opensslVersionRe.FindStringSubmatch(s); len(m) > 1 { version = m[1] } } return libsslPath, "v" + version } // selectGRPCServerProbe 根据 gRPC 版本选择服务端探针 func (t *Tracer) selectGRPCServerProbe(major, minor int) string { // 根据 gRPC 版本选择相应的探针 if major == 1 && minor >= 69 { // 现代版本 (>= 1.69.0) 使用新的探针 klog.Infof("Selecting modern gRPC server probe for version %d.%d", major, minor) return "uprobe_server_handleStream2" } else { // 传统版本 (< 1.69.0) 使用旧的探针 klog.Infof("Selecting legacy gRPC server probe for version %d.%d", major, minor) return "uprobe_server_handleStream" } } // attachUprobe 附加单个 uprobe,处理错误 func attachUprobe(exe *link.Executable, symbolName string, probeName string, uprobes map[string]*ebpf.Program, address uint64, onError string, returnOnError bool) (link.Link, error) { l, err := exe.Uprobe(symbolName, uprobes[probeName], &link.UprobeOptions{Address: address}) if err != nil { if returnOnError { klog.WithError(err).Errorf("failed to attach %s uprobe: %s", probeName, onError) return nil, err } else { klog.WithError(err).Errorf("failed to attach %s uprobe: %s", probeName, onError) return nil, nil } } return l, nil } // attachUprobeWithReturns 附加 uprobe 并附加返回探针 // enterProbeName 为空字符串时,只附加返回探针 func attachUprobeWithReturns(exe *link.Executable, symbolName string, enterProbeName, returnProbeName string, uprobes map[string]*ebpf.Program, address uint64, s elf.Symbol, textSection *elf.Section, textSectionLen uint64, textSectionData []byte, machine elf.Machine, onError string, returnOnError bool) ([]link.Link, error) { var links []link.Link // 附加入口探针(如果提供了入口探针名称) if enterProbeName != "" { l, err := attachUprobe(exe, symbolName, enterProbeName, uprobes, address, onError, returnOnError) if err != nil { return nil, err } if l == nil { return nil, nil } links = append(links, l) } // 计算符号在 text section 中的位置 sStart := s.Value - textSection.Addr sEnd := sStart + s.Size if sEnd > textSectionLen { return links, nil } // 读取符号字节码 sBytes := textSectionData[sStart:sEnd] returnOffsets := getReturnOffsets(machine, sBytes) if len(returnOffsets) == 0 { if returnOnError { err := fmt.Errorf("failed to attach %s: no return offsets found", returnProbeName) klog.Errorln(err) return nil, err } return links, nil } // 为每个返回点附加探针 for _, offset := range returnOffsets { l, err := exe.Uprobe(symbolName, uprobes[returnProbeName], &link.UprobeOptions{Address: address, Offset: uint64(offset)}) if err != nil { if returnOnError { klog.WithError(err).Errorf("failed to attach %s uprobe", returnProbeName) return nil, err } continue } links = append(links, l) } return links, nil } func getReturnOffsets(machine elf.Machine, instructions []byte) []int { var res []int switch machine { case elf.EM_X86_64: for i := 0; i < len(instructions); { ins, err := x86asm.Decode(instructions[i:], 64) if err == nil && ins.Op == x86asm.RET { res = append(res, i) } i += ins.Len } case elf.EM_AARCH64: for i := 0; i < len(instructions); { ins, err := arm64asm.Decode(instructions[i:]) if err == nil && ins.Op == arm64asm.RET { res = append(res, i) } i += 4 } } return res } func min(a, b int) int { if a < b { return a } return b }