golang與非golang程序探測(cè)beyla源碼解讀
beyla中g(shù)olang程序與非golang程序的ebpf探測(cè)
beyla中g(shù)olang程序與非golang程序的ebpf采用了不同的探測(cè)方式
- golang:使用uprobe監(jiān)聽(tīng)用戶庫(kù)函數(shù);
- 非golang:使用kprobe監(jiān)聽(tīng)內(nèi)核函數(shù);
程序類型的定義:
// beyla/pkg/internal/svc/svc.go type InstrumentableType int const ( InstrumentableGolang = InstrumentableType(iota) InstrumentableJava InstrumentableDotnet InstrumentablePython InstrumentableRuby InstrumentableNodejs InstrumentableRust InstrumentableGeneric )
一.程序的區(qū)分方法
對(duì)golang與非golang程序,其區(qū)分方法是讀elf可執(zhí)行文件,然后查找symbols是否包含golang的function,以go nethttp為例:
- 讀elf文件的symbols,然后查找其中是否有g(shù)o http的function;
http的function:
- "net/http.serverHandler.ServeHTTP"
- "net/http.(*conn).readRequest"
- "net/http.(*response).WriteHeader"
- "net/http.(*Transport).roundTrip"
源碼,重點(diǎn)是inspectOffset(execElf)函數(shù):
- 讀取elf文件,解析其中的symbols;
- 如果探測(cè)到golang的用戶函數(shù),則認(rèn)為是Golang程序;
// beyla/pkg/internal/discover/typer.go func (t *typer) asInstrumentable(execElf *exec.FileInfo) Instrumentable { ... // look for suitable Go application first offsets, ok := t.inspectOffsets(execElf) if ok { // we found go offsets, let's see if this application is not a proxy if !isGoProxy(offsets) { return Instrumentable{Type: svc.InstrumentableGolang, FileInfo: execElf, Offsets: offsets} } } ... detectedType := exec.FindProcLanguage(execElf.Pid, execElf.ELF) return Instrumentable{Type: detectedType, FileInfo: execElf, ChildPids: child} }
按照注冊(cè)監(jiān)聽(tīng)時(shí)的function列表,在elf中查找symbols:
- findGoSymbolTable(elfF)負(fù)責(zé)從elf中提取symbols;
// beyla/pkg/internal/goexec/instructions.go func instrumentationPoints(elfF *elf.File, funcNames []string) (map[string]FuncOffsets, error) { ... symTab, err := findGoSymbolTable(elfF) for _, f := range symTab.Funcs { ... if _, ok := functions[fName]; ok { offs, ok, err := findFuncOffset(&f, elfF) if ok { allOffsets[fName] = offs } } } return allOffsets, nil }
重點(diǎn)看一下findGoSymbolTable()函數(shù)的實(shí)現(xiàn):
- 首先,讀取elf文件中section=.gopclntab的內(nèi)容;
- 然后,讀取elf文件中section=.text的內(nèi)容;
- 最后,使用上面讀取的內(nèi)容,構(gòu)造symTab;
golang的這種elf結(jié)構(gòu),保證了:
- 即使elf被stripped,也能通過(guò) debug/gosym 庫(kù),將其中的symbols讀取出來(lái);
- debug/gosym 是Go標(biāo)準(zhǔn)庫(kù)中的一個(gè)包,用于解析Go程序的符號(hào)表信息;
// beyla/pkg/internal/goexec/instructions.go func findGoSymbolTable(elfF *elf.File) (*gosym.Table, error) { var err error var pclndat []byte // program counter line table if sec := elfF.Section(".gopclntab"); sec != nil { if pclndat, err = sec.Data(); err != nil { return nil, fmt.Errorf("acquiring .gopclntab data: %w", err) } } txtSection := elfF.Section(".text") pcln := gosym.NewLineTable(pclndat, txtSection.Addr) symTab, err := gosym.NewTable(nil, pcln) ... return symTab, nil }
看下elf中包含的sections:
- 可以看出,其中包gopclntab和text這兩個(gè)section;
# objdump -h example-http example-http 文件格式 elf64-x86-64 節(jié): Idx Name Size VMA LMA File off Algn 0 .text 0020e3e6 0000000000401000 0000000000401000 00001000 2**5 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .plt 00000260 000000000060f400 000000000060f400 0020f400 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .rodata 000e2060 0000000000610000 0000000000610000 00210000 2**5 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .dynsym 000003f0 00000000006f2940 00000000006f2940 002f2940 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA ... CONTENTS, ALLOC, LOAD, READONLY, DATA 12 .gosymtab 00000000 00000000006f4a90 00000000006f4a90 002f4a90 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 13 .gopclntab 001440b0 00000000006f4aa0 00000000006f4aa0 002f4aa0 2**5 CONTENTS, ALLOC, LOAD, READONLY, DATA 14 .go.buildinfo 00000140 0000000000839000 0000000000839000 00439000 2**4 CONTENTS, ALLOC, LOAD, DATA ... 24 .note.go.buildid 00000064 0000000000400f80 0000000000400f80 00000f80 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA
二.golang與非golang程序的監(jiān)聽(tīng)探針
golang與非golang程序使用了不同的ebpf監(jiān)聽(tīng)方法。
- golang程序:使用newGoTracerGroup()注冊(cè)tracer的方法;
- 非golang程序:使用newNonGoTracersGroup()注冊(cè)tracer的方法;
// beyla/pkg/internal/discover/attacher.go func (ta *TraceAttacher) getTracer(ie *Instrumentable) (*ebpf.ProcessTracer, bool) { ... var programs []ebpf.Tracer switch ie.Type { case svc.InstrumentableGolang: ... tracerType = ebpf.Go programs = filterNotFoundPrograms(newGoTracersGroup(ta.Cfg, ta.Metrics), ie.Offsets) case svc.InstrumentableJava, svc.InstrumentableNodejs, svc.InstrumentableRuby, svc.InstrumentablePython, svc.InstrumentableDotnet, svc.InstrumentableGeneric, svc.InstrumentableRust: ... programs = newNonGoTracersGroup(ta.Cfg, ta.Metrics) } ... }
1.golang程序的tracer:
// beyla/pkg/internal/discover/finder.go func newGoTracersGroup(cfg *pipe.Config, metrics imetrics.Reporter) []ebpf.Tracer { // Each program is an eBPF source: net/http, grpc... return []ebpf.Tracer{ nethttp.New(&cfg.EBPF, metrics), &nethttp.GinTracer{Tracer: *nethttp.New(&cfg.EBPF, metrics)}, grpc.New(&cfg.EBPF, metrics), goruntime.New(&cfg.EBPF, metrics), gosql.New(&cfg.EBPF, metrics), } }
以nethttp為例,ebpf監(jiān)聽(tīng)的用戶庫(kù)函數(shù):
// beyla/pkg/internal/ebpf/nethttp/nethttp.go func (p *Tracer) GoProbes() map[string]ebpfcommon.FunctionPrograms { return map[string]ebpfcommon.FunctionPrograms{ "net/http.serverHandler.ServeHTTP": { Start: p.bpfObjects.UprobeServeHTTP, }, "net/http.(*conn).readRequest": { End: p.bpfObjects.UprobeReadRequestReturns, }, "net/http.(*response).WriteHeader": { Start: p.bpfObjects.UprobeWriteHeader, }, "net/http.(*Transport).roundTrip": { // HTTP client, works with Client.Do as well as using the RoundTripper directly Start: p.bpfObjects.UprobeRoundTrip, End: p.bpfObjects.UprobeRoundTripReturn, }, } }
2.非golang程序的tracer:
// beyla/pkg/internal/discover/finder.go func newNonGoTracersGroup(cfg *pipe.Config, metrics imetrics.Reporter) []ebpf.Tracer { return []ebpf.Tracer{httpfltr.New(cfg, metrics), httpssl.New(cfg, metrics)} }
進(jìn)入到httpfltr查看監(jiān)聽(tīng)的kprobe函數(shù):
// beyla/pkg/internal/ebpf/httpfltr/httpfltr.go func (p *Tracer) KProbes() map[string]ebpfcommon.FunctionPrograms { return map[string]ebpfcommon.FunctionPrograms{ // Both sys accept probes use the same kretprobe. // We could tap into __sys_accept4, but we might be more prone to // issues with the internal kernel code changing. "sys_accept": { Required: true, End: p.bpfObjects.KretprobeSysAccept4, }, "sys_accept4": { Required: true, End: p.bpfObjects.KretprobeSysAccept4, }, "sock_alloc": { Required: true, End: p.bpfObjects.KretprobeSockAlloc, }, "tcp_rcv_established": { Required: true, Start: p.bpfObjects.KprobeTcpRcvEstablished, }, // Tracking of HTTP client calls, by tapping into connect "sys_connect": { Required: true, End: p.bpfObjects.KretprobeSysConnect, }, "tcp_connect": { Required: true, Start: p.bpfObjects.KprobeTcpConnect, }, "tcp_sendmsg": { Required: true, Start: p.bpfObjects.KprobeTcpSendmsg, }, // Reading more than 160 bytes "tcp_recvmsg": { Required: true, Start: p.bpfObjects.KprobeTcpRecvmsg, End: p.bpfObjects.KretprobeTcpRecvmsg, }, } }
參考:http://www.dbjr.com.cn/article/264383.htm
以上就是golang與非golang程序探測(cè)beyla源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于golang beyla程序探測(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺析Go語(yǔ)言中數(shù)組的這些細(xì)節(jié)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中數(shù)組一些細(xì)節(jié)的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以了解一下2022-11-11golang 實(shí)現(xiàn)一個(gè)負(fù)載均衡案例(隨機(jī),輪訓(xùn))
這篇文章主要介紹了golang 實(shí)現(xiàn)一個(gè)負(fù)載均衡案例(隨機(jī)、輪訓(xùn)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04使用Go語(yǔ)言玩轉(zhuǎn) RESTful API 服務(wù)
RESTful API是一種基于HTTP協(xié)議的API設(shè)計(jì)風(fēng)格,遵循REST架構(gòu)風(fēng)格,這篇文章主要為大家介紹了如何通過(guò)Go語(yǔ)言構(gòu)建RESTful API服務(wù),有需要的可以了解下2025-02-02詳解Go語(yǔ)言中切片的長(zhǎng)度與容量的區(qū)別
切片可以看成是數(shù)組的引用,切片的長(zhǎng)度是它所包含的元素個(gè)數(shù)。切片的容量是從它的第一個(gè)元素到其底層數(shù)組元素末尾的個(gè)數(shù)。本文將通過(guò)示例詳細(xì)講講Go語(yǔ)言中切片的長(zhǎng)度與容量的區(qū)別,需要的可以參考一下2022-11-11使用Go語(yǔ)言進(jìn)行安卓開(kāi)發(fā)的詳細(xì)教程
本文將介紹如何使用Go語(yǔ)言進(jìn)行安卓開(kāi)發(fā),我們將探討使用Go語(yǔ)言進(jìn)行安卓開(kāi)發(fā)的優(yōu)點(diǎn)、準(zhǔn)備工作、基本概念和示例代碼,通過(guò)本文的學(xué)習(xí),你將了解如何使用Go語(yǔ)言構(gòu)建高效的安卓應(yīng)用程序,需要的朋友可以參考下2023-11-11Golang記錄、計(jì)算函數(shù)執(zhí)行耗時(shí)、運(yùn)行時(shí)間的一個(gè)簡(jiǎn)單方法
這篇文章主要介紹了Golang記錄、計(jì)算函數(shù)執(zhí)行耗時(shí)、運(yùn)行時(shí)間的一個(gè)簡(jiǎn)單方法,本文直接給出代碼實(shí)例,需要的朋友可以參考下2015-07-07Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏的解決方案詳解
這篇文章主要為大家詳細(xì)介紹了當(dāng)Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏時(shí)蓋如何解決,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-07-07