diff --git a/asm/amd/interpreter.go b/asm/amd/interpreter.go index 7487e966b..fca2dd37b 100644 --- a/asm/amd/interpreter.go +++ b/asm/amd/interpreter.go @@ -80,13 +80,13 @@ func (i *Interpreter) Step() (x86asm.Inst, error) { switch inst.Op { case x86asm.ADD: if dst, ok := inst.Args[0].(x86asm.Reg); ok { - left := i.Regs.getX86asm(dst) + left := i.Regs.GetX86(dst) switch src := inst.Args[1].(type) { case x86asm.Imm: right := expression.Imm(uint64(src)) i.Regs.setX86asm(dst, expression.Add(left, right)) case x86asm.Reg: - right := i.Regs.getX86asm(src) + right := i.Regs.GetX86(src) i.Regs.setX86asm(dst, expression.Add(left, right)) case x86asm.Mem: right := i.MemArg(src) @@ -98,7 +98,7 @@ func (i *Interpreter) Step() (x86asm.Inst, error) { if dst, ok := inst.Args[0].(x86asm.Reg); ok { if src, imm := inst.Args[1].(x86asm.Imm); imm { v := expression.Multiply( - i.Regs.getX86asm(dst), + i.Regs.GetX86(dst), expression.Imm(uint64(math.Pow(2, float64(src)))), ) i.Regs.setX86asm(dst, v) @@ -110,7 +110,7 @@ func (i *Interpreter) Step() (x86asm.Inst, error) { case x86asm.Imm: i.Regs.setX86asm(dst, expression.Imm(uint64(src))) case x86asm.Reg: - i.Regs.setX86asm(dst, i.Regs.getX86asm(src)) + i.Regs.setX86asm(dst, i.Regs.GetX86(src)) case x86asm.Mem: v := i.MemArg(src) @@ -137,7 +137,7 @@ func (i *Interpreter) Step() (x86asm.Inst, error) { if dst, ok := inst.Args[0].(x86asm.Reg); ok { if src, imm := inst.Args[1].(x86asm.Imm); imm { if src == 3 { // todo other cases - i.Regs.setX86asm(dst, expression.ZeroExtend(i.Regs.getX86asm(dst), 2)) + i.Regs.setX86asm(dst, expression.ZeroExtend(i.Regs.GetX86(dst), 2)) } } } @@ -159,11 +159,11 @@ func (i *Interpreter) MemArg(src x86asm.Mem) expression.Expression { vs = append(vs, expression.Imm(uint64(src.Disp))) } if src.Base != 0 { - vs = append(vs, i.Regs.getX86asm(src.Base)) + vs = append(vs, i.Regs.GetX86(src.Base)) } if src.Index != 0 { v := expression.Multiply( - i.Regs.getX86asm(src.Index), + i.Regs.GetX86(src.Index), expression.Imm(uint64(src.Scale)), ) vs = append(vs, v) diff --git a/asm/amd/regs_state.go b/asm/amd/regs_state.go index 77af3cc91..a793220d0 100644 --- a/asm/amd/regs_state.go +++ b/asm/amd/regs_state.go @@ -215,7 +215,9 @@ func (r *Registers) setX86asm(reg x86asm.Reg, v expression.Expression) { r.regs[e.idx] = v } -func (r *Registers) getX86asm(reg x86asm.Reg) expression.Expression { +// GetX86 returns the expression.Expression value associated with the given x86asm.Reg, with +// appropriate zero-extension if necessary. +func (r *Registers) GetX86(reg x86asm.Reg) expression.Expression { e := regMappingFor(reg) res := r.regs[e.idx] if e.bits != 64 { @@ -224,6 +226,7 @@ func (r *Registers) getX86asm(reg x86asm.Reg) expression.Expression { return res } +// Get returns the expression.Expression value associated with the given Reg register func (r *Registers) Get(reg Reg) expression.Expression { if int(reg) >= len(r.regs) { return r.regs[0] diff --git a/asm/expression/expression.go b/asm/expression/expression.go index 8bc4ffff4..714d3efe5 100644 --- a/asm/expression/expression.go +++ b/asm/expression/expression.go @@ -60,9 +60,9 @@ func cmpOrder(u Expression) int { return 1 case *op: return 2 - case *ImmediateCapture: - return 3 case *named: + return 3 + case *ImmediateCapture: return 4 case *immediate: return 5 diff --git a/tpbase/assembly_decode_aarch64.go b/tpbase/assembly_decode_aarch64.go index 41d8906c7..31de50e97 100644 --- a/tpbase/assembly_decode_aarch64.go +++ b/tpbase/assembly_decode_aarch64.go @@ -11,7 +11,7 @@ import ( aa "golang.org/x/arch/arm64/arm64asm" ) -func arm64GetAnalyzers() []Analyzer { +func getAnalyzersARM() []Analyzer { return []Analyzer{ {"tls_set", analyzeTLSSetARM}, } diff --git a/tpbase/assembly_decode_arm64.go b/tpbase/assembly_decode_arm64.go deleted file mode 100644 index 695dce090..000000000 --- a/tpbase/assembly_decode_arm64.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build arm64 - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" - -func GetAnalyzers() []Analyzer { - return arm64GetAnalyzers() -} diff --git a/tpbase/assembly_decode_test.go b/tpbase/assembly_decode_test.go index 931b48742..6c070b740 100644 --- a/tpbase/assembly_decode_test.go +++ b/tpbase/assembly_decode_test.go @@ -1,5 +1,3 @@ -//go:build amd64 - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -257,9 +255,9 @@ func TestFSBase(t *testing.T) { var analyzers []Analyzer switch test.machine { case elf.EM_X86_64: - analyzers = x86GetAnalyzers() + analyzers = getAnalyzersX86() case elf.EM_AARCH64: - analyzers = arm64GetAnalyzers() + analyzers = getAnalyzersARM() } if analyzers == nil { t.Skip("tests not available on this platform") diff --git a/tpbase/assembly_decode_amd64.go b/tpbase/assembly_decode_x86.go similarity index 56% rename from tpbase/assembly_decode_amd64.go rename to tpbase/assembly_decode_x86.go index c8eb3d6df..3f77a9cfd 100644 --- a/tpbase/assembly_decode_amd64.go +++ b/tpbase/assembly_decode_x86.go @@ -1,5 +1,3 @@ -//go:build amd64 - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -9,51 +7,60 @@ import ( "bytes" "encoding/binary" "errors" - "unsafe" - _ "go.opentelemetry.io/ebpf-profiler/zydis" // links Zydis + "go.opentelemetry.io/ebpf-profiler/asm/amd" + e "go.opentelemetry.io/ebpf-profiler/asm/expression" + "golang.org/x/arch/x86/x86asm" ) -// #cgo CFLAGS: -g -Wall -// #include -// #include "fsbase_decode_amd64.h" -import "C" - -func x86GetAnalyzers() []Analyzer { +func getAnalyzersX86() []Analyzer { return []Analyzer{ - {"x86_fsbase_write_task", AnalyzeX86fsbaseWriteTask}, - {"aout_dump_debugregs", AnalyzeAoutDumpDebugregs}, + {"x86_fsbase_write_task", analyzefsbaseWriteTaskX86}, + {"aout_dump_debugregs", analyzeAoutDumpDebugregsX86}, } } -func GetAnalyzers() []Analyzer { - return x86GetAnalyzers() -} - -// AnalyzeAoutDumpDebugregs looks at the assembly of the `aout_dump_debugregs` function in the +// analyzeAoutDumpDebugregsX86 looks at the assembly of the `aout_dump_debugregs` function in the // kernel in order to compute the offset of `fsbase` into `task_struct`. -func AnalyzeAoutDumpDebugregs(code []byte) (uint32, error) { +func analyzeAoutDumpDebugregsX86(code []byte) (uint32, error) { if len(code) == 0 { return 0, errors.New("empty code blob passed to getFSBaseOffset") } - - // Because different compilers generate code that looks different enough, we disassemble the - // function in order to properly analyze the code and deduce the fsbase offset. - // The underlying logic uses the zydis library, hence the cgo call. - offset := uint32(C.decode_fsbase_aout_dump_debugregs( - (*C.uint8_t)(unsafe.Pointer(&code[0])), C.size_t(len(code)))) - - if offset == 0 { - return 0, errors.New("unable to determine fsbase offset") + it := amd.NewInterpreterWithCode(code) + offset := e.NewImmediateCapture("offset") + expected := e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.GS, e.NewImmediateCapture("")), + offset, + ), + ) + for { + op, err := it.Step() + if err != nil { + return 0, err + } + if op.Op != x86asm.MOV { + continue + } + dst, ok := op.Args[0].(x86asm.Reg) + if !ok { + continue + } + actual := it.Regs.GetX86(dst) + if actual.Match(expected) { + res := int64(offset.CapturedValue()) - 2*8 + if res < 0 || res > 256*1024 { + return 0, errors.New("failed to determine offset of fsbase") + } + return uint32(res), nil + } } - - return offset, nil } -// AnalyzeX86fsbaseWriteTask looks at the assembly of the function x86_fsbase_write_task which +// analyzefsbaseWriteTaskX86 looks at the assembly of the function x86_fsbase_write_task which // is ideal because it only writes the argument to the fsbase function. We can get the fsbase // offset directly from the assembly here. Available since kernel version 4.20. -func AnalyzeX86fsbaseWriteTask(code []byte) (uint32, error) { +func analyzefsbaseWriteTaskX86(code []byte) (uint32, error) { // Supported sequences (might be surrounded be additional code for the WARN_ONCE): // // 1) Alpine Linux (kernel 5.10+) diff --git a/tpbase/fsbase_decode_amd64.c b/tpbase/fsbase_decode_amd64.c deleted file mode 100644 index 22a51b4dc..000000000 --- a/tpbase/fsbase_decode_amd64.c +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -//go:build amd64 - -#include "../zydis/Zydis.h" -#include "fsbase_decode_amd64.h" - - -// decode_fsbase_aout_dump_debugregs attempts to compute the offset of `fsbase` in `task_struct` from the x86-64 -// assembly code of the `aout_dump_debugregs` function in the kernel, which existed up until kernel 5.9. -// It returns the fsbase offset if successful, or 0 on failure. -// aout_dump_debugregs code: see https://elixir.bootlin.com/linux/v5.9.16/source/arch/x86/kernel/hw_breakpoint.c#L452 -// -// This function expects 2 instructions to be present in the code blob: -// 1) A `mov` instruction loading the current task_struct address (recognizable with the GS segment being the base) into -// a target register. -// 2) A subsequent `mov` instruction loading the address of `task_struct->thread.ptrace_bps[i]`, the base register being -// the target register of the previous instruction. -// -// From 2) we can extract the offset of ptrace_bps in task_struct. -// The layout of `task_struct.thread` (see arch/x86/include/asm/processor.h) is: -// [...] -// unsigned long fsbase; -// unsigned long gsbase; -// struct perf_event *ptrace_bps[HBP_NUM]; -// [...] -// => we can then subtract 2*sizeof(unsigned long) to find the fsbase offset. -uint32_t decode_fsbase_aout_dump_debugregs(const uint8_t* code, size_t codesz) { - ZydisDecoder decoder; - ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); - - ZydisDecodedInstruction instr; - ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; - ZydisRegister target_register = ZYDIS_REGISTER_NONE; - - ZyanUSize instruction_offset = 0; - - // 1) Find the first `mov` with a `gs` base. By inspection of the C code, we assume it loads the address of the - // current `task_struct`. - while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code + instruction_offset, - codesz - instruction_offset, &instr, operands))) { - instruction_offset += instr.length; - - if (! (instr.attributes & ZYDIS_ATTRIB_HAS_SEGMENT_GS)) { - continue; - } - if (instr.mnemonic != ZYDIS_MNEMONIC_MOV) { - continue; - } - if (operands[0].type != ZYDIS_OPERAND_TYPE_REGISTER) { - continue; - } - // This instruction loads the address of the current task_struct into `target_register`. - target_register = operands[0].reg.value; - break; - } - - if (target_register == ZYDIS_REGISTER_NONE) { - return 0; - } - - int64_t lea_offset = 0; - int64_t mov_offset = 0; - - // 2) Find the first `mov` instruction that either uses `target_register` as base, or for which the base register is - // the result of a LEA that uses `target_register` as base. - // We assume that `mov` computes the address of `task_struct.thread.ptrace_bps` based on the `task_struct` address - // we expect to have loaded in 1). - while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code + instruction_offset, - codesz - instruction_offset, &instr, operands))) { - instruction_offset += instr.length; - - // Some compilers will emit LEA+MOV instead of MOV. - // In this case, we need to add offsets from both. - if (instr.mnemonic == ZYDIS_MNEMONIC_LEA) { - if (operands[1].type != ZYDIS_OPERAND_TYPE_MEMORY) { - continue; - } - if (operands[1].mem.base != target_register) { - continue; - } - if (lea_offset != 0) { - // We already found a matching LEA. A second one means we went too far. - return 0; - } - if (! operands[1].mem.disp.has_displacement) { - return 0; - } - if (operands[0].type != ZYDIS_OPERAND_TYPE_REGISTER) { - return 0; - } - // Update target register to be this LEA's target - target_register = operands[0].reg.value; - - lea_offset = operands[1].mem.disp.value; - continue; - } - - if (instr.mnemonic == ZYDIS_MNEMONIC_MOV) { - if (operands[1].type != ZYDIS_OPERAND_TYPE_MEMORY) { - continue; - } - if (operands[1].mem.base != target_register) { - continue; - } - if (! operands[1].mem.disp.has_displacement) { - return 0; - } - // The displacement is the offset of ptrace_bps in task_struct, minus any offset from a previous LEA instruction. - mov_offset = operands[1].mem.disp.value; - break; - } - } - - if (mov_offset == 0) { - return 0; - } - - int64_t result = lea_offset + mov_offset; - - // Compute the `fsbase` offset from the `ptrace_bps` offset, according to the `thread_struct` layout. - result -= 2*sizeof(long); - - if (result < 0 || result > UINT32_MAX) { - return 0; - } - - return (uint32_t)result; -} diff --git a/tpbase/fsbase_decode_amd64.h b/tpbase/fsbase_decode_amd64.h deleted file mode 100644 index fd4c63cbb..000000000 --- a/tpbase/fsbase_decode_amd64.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -//go:build amd64 - -#ifndef __FSBASE_DECODE_X86_64__ -#define __FSBASE_DECODE_X86_64__ - -#include - -uint32_t decode_fsbase_aout_dump_debugregs(const uint8_t* code, size_t codesz); - -#endif diff --git a/tpbase/libc.go b/tpbase/libc.go index 072780ae3..7e9b2a7a1 100644 --- a/tpbase/libc.go +++ b/tpbase/libc.go @@ -4,7 +4,7 @@ package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" import ( - "errors" + "debug/elf" "fmt" "regexp" @@ -77,9 +77,6 @@ type TSDInfo struct { var ( // regex for the libc libcRegex = regexp.MustCompile(`.*/(ld-musl|libc|libpthread)([-.].*)?\.so`) - - // error that a non-native architectures is not implemented (to skip tests) - errArchNotImplemented = errors.New("architecture not implemented") ) // IsPotentialTSDDSO determines if the DSO filename potentially contains pthread code @@ -100,7 +97,15 @@ func ExtractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { return nil, fmt.Errorf("getspecific function size is %d", len(code)) } - info, err := ExtractTSDInfoNative(code) + var info TSDInfo + switch ef.Machine { + case elf.EM_AARCH64: + info, err = extractTSDInfoARM(code) + case elf.EM_X86_64: + info, err = extractTSDInfoX86(code) + default: + return nil, fmt.Errorf("unsupported arch %s", ef.Machine.String()) + } if err != nil { return nil, fmt.Errorf("failed to extract getspecific data: %s", err) } diff --git a/tpbase/libc_amd64.go b/tpbase/libc_amd64.go deleted file mode 100644 index dce49d84f..000000000 --- a/tpbase/libc_amd64.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build amd64 - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" - -import ( - "errors" - "unsafe" - - _ "go.opentelemetry.io/ebpf-profiler/zydis" // links Zydis -) - -// #cgo CFLAGS: -g -Wall -// #include -// #include "libc_decode_amd64.h" -import "C" - -func ExtractTSDInfoX64_64(code []byte) (TSDInfo, error) { - // function in order to properly analyze the code and deduce the fsbase offset. - // The underlying logic uses the zydis library, hence the cgo call. - val := uint32(C.decode_pthread_getspecific( - (*C.uint8_t)(unsafe.Pointer(&code[0])), C.size_t(len(code)))) - - if val == 0 { - return TSDInfo{}, errors.New("unable to determine libc info") - } - - return TSDInfo{ - Offset: int16(val & 0xffff), - Multiplier: uint8(val >> 16), - Indirect: uint8((val >> 24) & 1), - }, nil -} - -func ExtractTSDInfoNative(code []byte) (TSDInfo, error) { - return ExtractTSDInfoX64_64(code) -} diff --git a/tpbase/libc_arm64.go b/tpbase/libc_arm64.go deleted file mode 100644 index 6ccc56a15..000000000 --- a/tpbase/libc_arm64.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build arm64 - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" - -func ExtractTSDInfoX64_64(_ []byte) (TSDInfo, error) { - return TSDInfo{}, errArchNotImplemented -} - -func ExtractTSDInfoNative(code []byte) (TSDInfo, error) { - return extractTSDInfoARM(code) -} diff --git a/tpbase/libc_decode_amd64.c b/tpbase/libc_decode_amd64.c deleted file mode 100644 index 4b232cf02..000000000 --- a/tpbase/libc_decode_amd64.c +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -//go:build amd64 - -#include "../zydis/Zydis.h" -#include "libc_decode_amd64.h" - -//#define DEBUG - -#ifdef DEBUG -#include -#endif - -#define MAX(a, b) ((a)>(b) ? (a) : (b)) - -enum { - Unspec = 0, - TSDBase, - TSDElementBase, - TSDIndex, - TSDValue, -}; - -typedef struct regInfo { - uint8_t state; - uint8_t multiplier; - uint8_t indirect; - int16_t offset; -} regInfo; - -static int32_t reg2ndx(ZydisRegister reg) -{ - reg = ZydisRegisterGetLargestEnclosing(ZYDIS_MACHINE_MODE_LONG_64, reg); - if (reg >= ZYDIS_REGISTER_RAX && reg <= ZYDIS_REGISTER_R15) - return reg - ZYDIS_REGISTER_RAX + 1; - return 0; -} - -uint32_t decode_pthread_getspecific(const uint8_t* code, size_t codesz) { - ZydisDecoder decoder; - ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; - ZydisDecodedInstruction instr; - regInfo regs[18] = {}; - int32_t destNdx = -1, srcNdx, indexNdx; - - // RDI = first argument = key index - regs[reg2ndx(ZYDIS_REGISTER_RDI)] = (regInfo) { .state = TSDIndex, .multiplier = 1 }; - - ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); - - for (ZyanUSize offs = 0 - ; ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code + offs, codesz - offs, &instr, operands)) - ; offs += instr.length) { -#ifdef DEBUG - if (destNdx >= 0 && destNdx < 32) { - fprintf(stderr, "r%02d state=%d, offs=%#x, mult=%d\n", - destNdx, regs[destNdx].state, regs[destNdx].offset, regs[destNdx].multiplier); - } -#endif - destNdx = -1; - if (operands[0].type != ZYDIS_OPERAND_TYPE_REGISTER) { - continue; - } - - destNdx = reg2ndx(operands[0].reg.value); - switch (instr.mnemonic) { - case ZYDIS_MNEMONIC_SHL: - regs[destNdx].offset <<= operands[1].imm.value.u; - regs[destNdx].multiplier <<= operands[1].imm.value.u; - continue; - - case ZYDIS_MNEMONIC_ADD: - if ((instr.attributes & ZYDIS_ATTRIB_HAS_SEGMENT_FS) && - regs[destNdx].state == TSDIndex) { - regs[destNdx].state = TSDElementBase; - continue; - } - if (operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) { - srcNdx = reg2ndx(operands[1].reg.value); - if ((regs[destNdx].state == TSDBase && regs[srcNdx].state == TSDIndex) || - (regs[destNdx].state == TSDIndex && regs[srcNdx].state == TSDBase)) { - regs[destNdx].offset += regs[srcNdx].offset; - // The register in TSDBase state has multiplier unset. This selects the - // multiplier of TSDIndex register. - regs[destNdx].multiplier = MAX(regs[destNdx].multiplier, regs[srcNdx].multiplier); - regs[destNdx].state = TSDElementBase; - continue; - } - } else if (operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { - regs[destNdx].offset += operands[1].imm.value.u; - continue; - } - break; - - case ZYDIS_MNEMONIC_LEA: - srcNdx = reg2ndx(operands[1].mem.base); - if (regs[srcNdx].state == TSDIndex) { - if (operands[1].mem.index == ZYDIS_REGISTER_NONE) { - regs[destNdx] = (regInfo) { - .state = TSDIndex, - .offset = regs[srcNdx].offset + operands[1].mem.disp.value, - .multiplier = regs[srcNdx].multiplier, - }; - continue; - } - } else if (regs[srcNdx].state == TSDBase) { - indexNdx = reg2ndx(operands[1].mem.index); - if (regs[indexNdx].state == TSDIndex) { - regs[destNdx] = (regInfo) { - .state = TSDElementBase, - .offset = regs[srcNdx].offset + regs[indexNdx].offset + operands[1].mem.disp.value, - .multiplier = regs[indexNdx].multiplier * (operands[1].mem.scale ?: 1), - }; - continue; - } - } - break; - - case ZYDIS_MNEMONIC_MOV: - if (instr.attributes & ZYDIS_ATTRIB_HAS_SEGMENT_FS) { - regs[destNdx] = (regInfo) { .state = TSDBase }; - continue; - } - if (operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) { - srcNdx = reg2ndx(operands[1].reg.value); - regs[destNdx] = regs[srcNdx]; - continue; - } - if (operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY) { - srcNdx = reg2ndx(operands[1].mem.base); - indexNdx = reg2ndx(operands[1].mem.index); - if (regs[srcNdx].state == TSDBase) { - if (operands[1].mem.index == ZYDIS_REGISTER_NONE) { - regs[destNdx] = (regInfo) { - .state = TSDBase, - .offset = operands[1].mem.disp.value, - .indirect = 1, - }; - continue; - } else if (regs[indexNdx].state == TSDIndex) { - regs[destNdx] = (regInfo) { - .state = TSDValue, - .offset = regs[srcNdx].offset, - .indirect = regs[srcNdx].indirect, - .multiplier = operands[1].mem.scale, - }; - continue; - } - } else if (regs[srcNdx].state == TSDElementBase) { - regs[destNdx] = (regInfo) { - .state = TSDValue, - .offset = regs[srcNdx].offset + operands[1].mem.disp.value, - .indirect = regs[srcNdx].indirect, - .multiplier = regs[srcNdx].multiplier * (operands[1].mem.scale ?: 1), - }; - continue; - } - } - break; - - case ZYDIS_MNEMONIC_RET: - // Return value is in RAX - srcNdx = reg2ndx(ZYDIS_REGISTER_RAX); - if (regs[srcNdx].state != TSDValue) - return 0; - - return (uint16_t)regs[srcNdx].offset | - ((uint32_t)regs[srcNdx].multiplier << 16) | - ((uint32_t)regs[srcNdx].indirect << 24); - - case ZYDIS_MNEMONIC_CMP: - case ZYDIS_MNEMONIC_TEST: - // Opcodes without effect to destNdx. - continue; - - default: - break; - } - - // Unsupported opcode. Assume it modified the operand 0, and mark it unknown. - regs[destNdx] = (regInfo) { .state = Unspec }; - } - return 0; -} diff --git a/tpbase/libc_decode_amd64.h b/tpbase/libc_decode_amd64.h deleted file mode 100644 index f982f785f..000000000 --- a/tpbase/libc_decode_amd64.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -//go:build amd64 - -#ifndef LIBC_DECODE_X86_64 -#define LIBC_DECODE_X86_64 - -#include - -uint32_t decode_pthread_getspecific(const uint8_t* code, size_t codesz); - -#endif diff --git a/tpbase/libc_test.go b/tpbase/libc_test.go index 98b194d41..05b7f1ae9 100644 --- a/tpbase/libc_test.go +++ b/tpbase/libc_test.go @@ -5,7 +5,6 @@ package tpbase import ( "debug/elf" - "errors" "testing" "github.com/stretchr/testify/assert" @@ -256,13 +255,10 @@ func TestExtractTSDInfo(t *testing.T) { var err error switch test.machine { case elf.EM_X86_64: - info, err = ExtractTSDInfoX64_64(test.code) + info, err = extractTSDInfoX86(test.code) case elf.EM_AARCH64: info, err = extractTSDInfoARM(test.code) } - if errors.Is(err, errArchNotImplemented) { - t.Skip("tests not available on this platform") - } if assert.NoError(t, err) { assert.Equal(t, test.info, info, "Wrong TSD info extraction") } diff --git a/tpbase/libc_x86.go b/tpbase/libc_x86.go new file mode 100644 index 000000000..8d1c839f8 --- /dev/null +++ b/tpbase/libc_x86.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +import ( + "errors" + + "go.opentelemetry.io/ebpf-profiler/asm/amd" + e "go.opentelemetry.io/ebpf-profiler/asm/expression" + "golang.org/x/arch/x86/x86asm" +) + +func extractTSDInfoX86(code []byte) (TSDInfo, error) { + it := amd.NewInterpreterWithCode(code) + key := it.Regs.Get(amd.RDI) + _, err := it.LoopWithBreak(func(op x86asm.Inst) bool { + return op.Op == x86asm.RET + }) + if err != nil { + return TSDInfo{}, err + } + res := it.Regs.Get(amd.RAX) + var ( + multiplier = e.NewImmediateCapture("multiplier") + multiplier2 = e.NewImmediateCapture("multiplier2") + offset = e.NewImmediateCapture("offset") + ) + + expected := e.Mem8( + e.Add( + e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0)), + offset, + ), + ), + e.Multiply( + e.ZeroExtend32(key), + multiplier), + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 1, + }, nil + } + expected = e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), + e.Multiply(e.ZeroExtend32(key), multiplier), + offset, + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 0, + }, nil + } + expected = e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), + e.Multiply( + e.ZeroExtend32(e.Add(key, multiplier2)), + multiplier, + ), + offset, + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(multiplier.CapturedValue()*multiplier2.CapturedValue() + + offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 0, + }, nil + } + return TSDInfo{}, errors.New("could not extract tsdInfo amd") +} diff --git a/tpbase/tpbase.go b/tpbase/tpbase.go index f9aea289a..11aa1c035 100644 --- a/tpbase/tpbase.go +++ b/tpbase/tpbase.go @@ -9,6 +9,22 @@ package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +import ( + "fmt" + "runtime" +) + +func GetAnalyzers() ([]Analyzer, error) { + switch runtime.GOARCH { + case "amd64": + return getAnalyzersX86(), nil + case "arm64": + return getAnalyzersARM(), nil + default: + return nil, fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) + } +} + type Analyzer struct { // FunctionName is the kernel function which can be analyzed FunctionName string diff --git a/tracer/tpbase.go b/tracer/tpbase.go index 5a797c74e..463ebb775 100644 --- a/tracer/tpbase.go +++ b/tracer/tpbase.go @@ -38,7 +38,11 @@ import ( func loadTPBaseOffset(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map, kmod *kallsyms.Module) (uint64, error) { var tpbaseOffset uint32 - for _, analyzer := range tpbase.GetAnalyzers() { + analyzers, err := tpbase.GetAnalyzers() + if err != nil { + return 0, err + } + for _, analyzer := range analyzers { sym, err := kmod.LookupSymbol(analyzer.FunctionName) if err != nil { continue