diff --git a/.azure-pipelines/steps/update-github-pipeline-status.yml b/.azure-pipelines/steps/update-github-pipeline-status.yml index aa830726f312..143f184f10ec 100644 --- a/.azure-pipelines/steps/update-github-pipeline-status.yml +++ b/.azure-pipelines/steps/update-github-pipeline-status.yml @@ -39,6 +39,7 @@ stages: - integration_tests_linux_debugger - profiler_integration_tests_windows - profiler_integration_tests_linux + - profiler_integration_tests_arm64 - asan_profiler_tests - ubsan_profiler_tests - tsan_profiler_tests @@ -118,6 +119,7 @@ stages: in(dependencies.integration_tests_linux_debugger.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.profiler_integration_tests_windows.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.profiler_integration_tests_linux.result, 'Succeeded','SucceededWithIssues','Skipped'), + in(dependencies.profiler_integration_tests_arm64.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.asan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.ubsan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.tsan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), @@ -209,6 +211,7 @@ stages: - integration_tests_linux_debugger - profiler_integration_tests_windows - profiler_integration_tests_linux + - profiler_integration_tests_arm64 - asan_profiler_tests - ubsan_profiler_tests - tsan_profiler_tests @@ -288,6 +291,7 @@ stages: in(dependencies.integration_tests_linux_debugger.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.profiler_integration_tests_windows.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.profiler_integration_tests_linux.result, 'Succeeded','SucceededWithIssues','Skipped'), + in(dependencies.profiler_integration_tests_arm64.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.asan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.ubsan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), in(dependencies.tsan_profiler_tests.result, 'Succeeded','SucceededWithIssues','Skipped'), diff --git a/.azure-pipelines/ultimate-pipeline.yml b/.azure-pipelines/ultimate-pipeline.yml index c29d8ee00ad3..797602dd6ced 100644 --- a/.azure-pipelines/ultimate-pipeline.yml +++ b/.azure-pipelines/ultimate-pipeline.yml @@ -2905,6 +2905,107 @@ stages: testResultsFiles: profiler/build_data/results/**/*.trx condition: succeededOrFailed() +- stage: profiler_integration_tests_arm64 + condition: > + and( + succeeded(), + or( + eq(variables.isMainOrReleaseBranch, true), + eq(dependencies.generate_variables.outputs['generate_variables_job.generate_variables_step.IsProfilerChanged'], 'True') + ) + ) + dependsOn: [package_arm64, generate_variables, merge_commit_id] + variables: + targetShaId: $[ stageDependencies.merge_commit_id.fetch.outputs['set_sha.sha']] + targetBranch: $[ stageDependencies.merge_commit_id.fetch.outputs['set_sha.branch']] + jobs: + - template: steps/update-github-status-jobs.yml + parameters: + jobs: [Test] + + - job: Test + timeoutInMinutes: 60 #default value + strategy: + matrix: + arm64: + baseImage: debian + artifactSuffix: linux-arm64 + alpine: + baseImage: alpine + artifactSuffix: linux-musl-arm64 + + variables: + IncludeMinorPackageVersions: $[eq(variables.perform_comprehensive_testing, 'true')] + + pool: + name: $(linuxArm64Pool) + + steps: + - template: steps/clone-repo.yml + parameters: + targetShaId: $(targetShaId) + targetBranch: $(targetBranch) + + - template: steps/restore-working-directory.yml + parameters: + artifact: build-$(artifactSuffix)-working-directory + + - template: steps/download-artifact.yml + parameters: + artifact: linux-monitoring-home-$(artifactSuffix) + path: $(monitoringHome) + + - template: steps/download-artifact.yml + parameters: + artifact: linux-profiler-symbols-$(artifactSuffix) + path: $(monitoringHome) + + - template: steps/run-in-docker.yml + parameters: + build: true + baseImage: $(baseImage) + command: "BuildProfilerSamples" + apiKey: $(DD_LOGGER_DD_API_KEY) + retryCountForRunCommand: 3 + + - template: steps/run-in-docker.yml + parameters: + baseImage: $(baseImage) + command: "BuildAndRunProfilerCpuLimitTests" + extraArgs: "--cpus 2 --env CONTAINER_CPUS=1" + apiKey: $(DD_LOGGER_DD_API_KEY) + retryCountForRunCommand: 3 + + - template: steps/run-in-docker.yml + parameters: + baseImage: $(baseImage) + command: "BuildAndRunProfilerCpuLimitTests" + extraArgs: "--cpus 0.5 --env CONTAINER_CPUS=0.5" + apiKey: $(DD_LOGGER_DD_API_KEY) + retryCountForRunCommand: 3 + + - script: | + docker-compose -f docker-compose.yml -p $(DockerComposeProjectName) \ + run --rm \ + -e baseImage=$(baseImage) \ + ProfilerIntegrationTests + displayName: docker-compose run --no-deps ProfilerIntegrationTests + env: + DD_LOGGER_DD_API_KEY: $(ddApiKey) + baseImage: $(baseImage) # for interpolation in the docker-compose file + + - publish: profiler/build_data + artifact: _$(System.StageName)_$(Agent.JobName)_logs_$(System.JobAttempt) + condition: always() + continueOnError: true + + - task: PublishTestResults@2 + displayName: publish test results + inputs: + testResultsFormat: VSTest + testResultsFiles: profiler/build_data/results/**/*.trx + condition: succeededOrFailed() + - stage: asan_profiler_tests #address sanitizer tests condition: > diff --git a/BUILDME b/BUILDME new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/build/cmake/FindLibunwind.cmake b/build/cmake/FindLibunwind.cmake index bb6cddf5699e..89b9b0e8b133 100644 --- a/build/cmake/FindLibunwind.cmake +++ b/build/cmake/FindLibunwind.cmake @@ -1,10 +1,10 @@ -SET(LIBUNWIND_VERSION "v1.8.1-custom-2") +SET(LIBUNWIND_VERSION "v1.8.1-custom-3") SET(LIBUNWIND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/libunwind-prefix/src/libunwind-build) ExternalProject_Add(libunwind GIT_REPOSITORY https://github.com/DataDog/libunwind.git - GIT_TAG kevin/v1.8.1-custom-2 + GIT_TAG gleocadie/v1.8.1-custom-3 GIT_PROGRESS true INSTALL_COMMAND "" UPDATE_COMMAND "" diff --git a/profiler/src/Demos/Directory.Build.props b/profiler/src/Demos/Directory.Build.props index 06bc6d1f79af..9a9f750a3643 100644 --- a/profiler/src/Demos/Directory.Build.props +++ b/profiler/src/Demos/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -7,7 +7,8 @@ net48;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 - netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + net6.0;net7.0;net8.0;net9.0;net10.0 AnyCPU;x64;x86 diff --git a/profiler/src/Demos/Samples.BuggyBits/Samples.BuggyBits.csproj b/profiler/src/Demos/Samples.BuggyBits/Samples.BuggyBits.csproj index bb0ef4a35992..d9bf5660cab5 100644 --- a/profiler/src/Demos/Samples.BuggyBits/Samples.BuggyBits.csproj +++ b/profiler/src/Demos/Samples.BuggyBits/Samples.BuggyBits.csproj @@ -1,7 +1,9 @@ - + - netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + net6.0;net7.0;net8.0;net9.0;net10.0 AnyCPU;x64;x86 diff --git a/profiler/src/Demos/Samples.Website-AspNetCore01/Samples.Website-AspNetCore01.csproj b/profiler/src/Demos/Samples.Website-AspNetCore01/Samples.Website-AspNetCore01.csproj index 62de37bebb41..4980e5aa7393 100644 --- a/profiler/src/Demos/Samples.Website-AspNetCore01/Samples.Website-AspNetCore01.csproj +++ b/profiler/src/Demos/Samples.Website-AspNetCore01/Samples.Website-AspNetCore01.csproj @@ -1,7 +1,9 @@ - + - netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + net6.0;net7.0;net8.0;net9.0;net10.0 Samples.Website_AspNetCore01 AnyCPU;x64;x86 diff --git a/profiler/src/ProfilerEngine/Datadog.Linux.ApiWrapper/functions_to_wrap.c b/profiler/src/ProfilerEngine/Datadog.Linux.ApiWrapper/functions_to_wrap.c index 4dbd8fc55af3..a8802b94aba4 100644 --- a/profiler/src/ProfilerEngine/Datadog.Linux.ApiWrapper/functions_to_wrap.c +++ b/profiler/src/ProfilerEngine/Datadog.Linux.ApiWrapper/functions_to_wrap.c @@ -673,6 +673,36 @@ pid_t fork() } #endif #endif + +static int (*__real_sigaction)(int signum, const struct sigaction* act, struct sigaction* oldact) = NULL; +static int (*__real_pthread_sigmask)(int how, const sigset_t *set, sigset_t *oldset) = NULL; + +int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact) +{ + check_init(); + if (signum == SIGSEGV && act != NULL) + { + struct sigaction new_act = *act; + sigaddset(&new_act.sa_mask, SIGPROF); + sigaddset(&new_act.sa_mask, SIGUSR1); + return __real_sigaction(signum, &new_act, oldact); + } + return __real_sigaction(signum, act, oldact); +} + +int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset) +{ + check_init(); + if (how == SIG_UNBLOCK && set != NULL && 1 == sigismember(set, SIGSEGV)) + { + sigset_t new_set = *set; + sigaddset(&new_set, SIGPROF); + sigaddset(&new_set, SIGUSR1); + return __real_pthread_sigmask(how, &new_set, oldset); + } + return __real_pthread_sigmask(how, set, oldset); +} + static pthread_once_t once_control = PTHREAD_ONCE_INIT; static void init() @@ -682,6 +712,8 @@ static void init() __real_dlclose = __dd_dlsym(RTLD_NEXT, "dlclose"); __real_dladdr = __dd_dlsym(RTLD_NEXT, "dladdr"); __real_execve = __dd_dlsym(RTLD_NEXT, "execve"); + __real_sigaction = __dd_dlsym(RTLD_NEXT, "sigaction"); + __real_pthread_sigmask = __dd_dlsym(RTLD_NEXT, "pthread_sigmask"); #ifdef DD_ALPINE __real_pthread_create = __dd_dlsym(RTLD_NEXT, "pthread_create"); __real_pthread_attr_init = __dd_dlsym(RTLD_NEXT, "pthread_attr_init"); diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.cpp index 7f2f13b86ae9..5985ecd41514 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.cpp @@ -8,7 +8,9 @@ Backtrace2Unwinder::Backtrace2Unwinder() = default; -std::int32_t Backtrace2Unwinder::Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize) const +std::int32_t Backtrace2Unwinder::Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize, + std::uintptr_t stackBase, std::uintptr_t stackEnd, + UnwinderTracer* tracer) const { // unw_backtrace2 handles the case ctx == nullptr auto* context = reinterpret_cast(ctx); diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.h index 9ce4b6578449..a6c9f8ac94c7 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/Backtrace2Unwinder.h @@ -13,6 +13,8 @@ class Backtrace2Unwinder : public IUnwinder ~Backtrace2Unwinder() override = default; // Returns the number of frames unwound - std::int32_t Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize) const override; + std::int32_t Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize, + std::uintptr_t stackBase = 0, std::uintptr_t stackEnd = 0, + UnwinderTracer* tracer = nullptr) const override; }; \ No newline at end of file diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/CMakeLists.txt b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/CMakeLists.txt index b1fd4f12ef3b..c70ea550a656 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/CMakeLists.txt +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/CMakeLists.txt @@ -90,6 +90,13 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${DEPLOY_DIR}) FILE(GLOB LINUX_PROFILER_SRC CONFIGURE_DEPENDS "*.cpp") +if (ISARM64) + list(REMOVE_ITEM LINUX_PROFILER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/Backtrace2Unwinder.cpp") +else() + list(REMOVE_ITEM LINUX_PROFILER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/HybridUnwinder.cpp") + list(REMOVE_ITEM LINUX_PROFILER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/UnwinderTracer.cpp") +endif() + FILE(GLOB COMMON_PROFILER_SRC LIST_DIRECTORIES false "../Datadog.Profiler.Native/*.cpp") FILE(GLOB EXCLUDE_DLLMAIN "../Datadog.Profiler.Native/DllMain.cpp") diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.cpp new file mode 100644 index 000000000000..d5d0fb4a934d --- /dev/null +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.cpp @@ -0,0 +1,228 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc. + +#include "HybridUnwinder.h" +#include "ManagedCodeCache.h" + +#include "UnwinderTracer.h" + +#define UNW_LOCAL_ONLY +#include + +#ifndef ARM64 +#error "HybridUnwinder is only supported on aarch64" +#endif + +#define UNW_REG_FP UNW_AARCH64_X29 + +static inline bool IsValidFp(uintptr_t fp, uintptr_t prevFp, + uintptr_t stackBase, uintptr_t stackEnd) +{ + if (fp == 0) + return false; + + if (fp % sizeof(void*) != 0) + return false; + + // Ensure the full frame record [fp, fp+16) lies within the stack. + if (fp < stackBase || fp + 2 * sizeof(void*) > stackEnd) + return false; + + // Stack grows down on arm64: FP chain must grow toward higher addresses. + if (prevFp != 0 && fp <= prevFp) + return false; + + return true; +} + +HybridUnwinder::HybridUnwinder(ManagedCodeCache* managedCodeCache) : + _codeCache(managedCodeCache) +{ +} + +std::int32_t HybridUnwinder::Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize, + uintptr_t stackBase, uintptr_t stackEnd, + UnwinderTracer* tracer) const +{ + if (bufferSize == 0) [[unlikely]] + { + return 0; + } + + if (tracer) tracer->Record(EventType::Start); + + auto* context = reinterpret_cast(ctx); + auto flag = static_cast(UNW_INIT_SIGNAL_FRAME); + + unw_context_t localContext; + if (ctx == nullptr) + { + flag = static_cast(0); + if (auto getResult =unw_getcontext(&localContext) != 0) + { + if (tracer) tracer->RecordFinish(getResult, FinishReason::FailedGetContext); + return -1; + } + context = &localContext; + } + + unw_cursor_t cursor; + auto initResult = unw_init_local2(&cursor, context, flag); + if (tracer) tracer->Record(EventType::InitCursor, initResult, cursor); + if (initResult != 0) + { + if (tracer) tracer->RecordFinish(initResult, FinishReason::FailedInitLocal2); + return -1; + } + + // === Phase 1: Walk native frames with libunwind until managed code is reached === + std::size_t i = 0; + unw_word_t ip = 0; + while (true) + { + if (auto getResult = unw_get_reg(&cursor, UNW_REG_IP, &ip) != 0 || ip == 0) + { + if (tracer) tracer->RecordFinish(getResult, FinishReason::FailedGetReg); + return i; + } + + if (_codeCache->IsManaged(ip)) + { + if (tracer) + { + unw_word_t managedFp = 0; + unw_get_reg(&cursor, UNW_REG_FP, &managedFp); + tracer->Record(EventType::ManagedTransition, ip, managedFp); + } + break; + } + + if (tracer) + { + unw_word_t sp = 0; + unw_word_t nativeFp = 0; + unw_get_reg(&cursor, UNW_AARCH64_SP, &sp); + unw_get_reg(&cursor, UNW_REG_FP, &nativeFp); + tracer->Record(EventType::NativeFrame, ip, nativeFp, sp); + } + + buffer[i++] = ip; + if (i >= bufferSize) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::BufferFull); + return i; + } + + auto stepResult = unw_step(&cursor); + if (tracer) tracer->Record(EventType::LibunwindStep, stepResult, cursor); + if (stepResult <= 0) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::FailedLibunwindStep); + return i; + } + } + + if (i >= bufferSize) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::BufferFull); + return i; + } + + // === Phase 2: Walk managed frames using the FP chain === + // The .NET JIT on arm64 always emits a frame record [prev_fp, saved_lr] for + // every managed method, so FP chaining is reliable once we enter managed code. + buffer[i++] = ip; + if (i >= bufferSize) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::BufferFull); + return i; + } + + if (stackBase == 0 || stackEnd == 0) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::NoStackBounds); + return i; + } + + unw_word_t fp = 0; + if (unw_get_reg(&cursor, UNW_REG_FP, &fp) != 0 || !IsValidFp(fp, 0, stackBase, stackEnd)) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::InvalidFp); + return i; + } + + // When libunwind falls back to LR-only (no DWARF, no frame record found for the + // last native frame), IP is set from X30 but X29 (FP) is left unchanged, pointing + // to that native frame rather than to the first managed frame. + // That native frame's saved LR at [FP+8] is the raw return address into managed + // code, which equals the raw cursor IP. Note: when ctx==nullptr (flag=0, + // use_prev_instr=1), unw_get_reg(IP) returns cursor.ip-1, so we compare against + // ip+1 to recover the raw value. + const uintptr_t rawIp = (ctx == nullptr) ? (ip + 1) : ip; + const auto firstLr = *reinterpret_cast(fp + sizeof(void*)); + if (firstLr == rawIp) + { + const uintptr_t staleFp = fp; + fp = *reinterpret_cast(staleFp); + if (!IsValidFp(fp, staleFp, stackBase, stackEnd)) + { + if (tracer) tracer->RecordFinish(static_cast(i), FinishReason::InvalidFp); + return i; + } + } + + // Walk the FP chain, skipping non-managed (native/stub) frames. + // In .NET 10+, user managed code calls throw via 3 native frames before reaching + // the managed RhThrowEx: + // IL_Throw (asm stub) → IL_Throw_Impl (C++) → DispatchManagedException (C++) → RhThrowEx (managed) + // In .NET 9, SoftwareExceptionFrame::Init() additionally calls PAL_VirtualUnwind(), + // which adds 1–3 extra native frames, bringing the total to 5–6. + // We must skip these native frames rather than stopping, or we lose the caller frame. + // The limit of 8 consecutive non-managed frames (6 + 2 margin) stops useless walking + // once we leave the managed portion of the stack entirely (e.g., thread startup code). + uintptr_t prevFp = 0; + int consecutiveNativeFrames = 0; + FinishReason finishReason = FinishReason::Success; + while (true) + { + ip = *reinterpret_cast(fp + sizeof(void*)); + if (ip == 0) + { + finishReason = FinishReason::InvalidIp; + break; + } + + if (tracer) tracer->Record(EventType::FrameChainStep, ip, fp); + + if (_codeCache->IsManaged(ip)) + { + if (i >= bufferSize) + { + finishReason = FinishReason::BufferFull; + break; + } + buffer[i++] = ip; + consecutiveNativeFrames = 0; + } + else + { + // Try 20 to see if CI fails or not. + if (++consecutiveNativeFrames > 20) + { + finishReason = FinishReason::TooManyNativeFrames; + break; + } + } + + prevFp = fp; + fp = *reinterpret_cast(fp); + if (!IsValidFp(fp, prevFp, stackBase, stackEnd)) + { + finishReason = FinishReason::InvalidFp; + break; + } + } + + if (tracer) tracer->RecordFinish(static_cast(i), finishReason); + return i; +} diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.h new file mode 100644 index 000000000000..f94a6553da67 --- /dev/null +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/HybridUnwinder.h @@ -0,0 +1,22 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc. + +#pragma once + +#include "IUnwinder.h" + +class ManagedCodeCache; + +class HybridUnwinder: public IUnwinder +{ +public: + HybridUnwinder(ManagedCodeCache* managedCodeCache); + ~HybridUnwinder() override = default; + + std::int32_t Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize, + std::uintptr_t stackBase = 0, std::uintptr_t stackEnd = 0, + UnwinderTracer* tracer = nullptr) const override; + +private: + ManagedCodeCache* _codeCache; +}; \ No newline at end of file diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/IUnwinder.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/IUnwinder.h index f339e9be7e75..6eac687666c1 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/IUnwinder.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/IUnwinder.h @@ -2,11 +2,15 @@ #include +class UnwinderTracer; + class IUnwinder { public: virtual ~IUnwinder() = default; // Returns the number of frames unwound - virtual std::int32_t Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize) const = 0; + virtual std::int32_t Unwind(void* ctx, std::uintptr_t* buffer, std::size_t bufferSize, + std::uintptr_t stackBase = 0, std::uintptr_t stackEnd = 0, + UnwinderTracer* tracer = nullptr) const = 0; }; \ No newline at end of file diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.cpp index 2a6f5f001ab3..6c1ddc9c74c2 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.cpp @@ -24,6 +24,10 @@ #include "ScopeFinalizer.h" #include "StackSnapshotResultBuffer.h" +#ifdef ARM64 +#include "UnwinderTracer.h" +#endif + using namespace std::chrono_literals; std::mutex LinuxStackFramesCollector::s_stackWalkInProgressMutex; @@ -41,7 +45,8 @@ LinuxStackFramesCollector::LinuxStackFramesCollector( _processId{OpSysTools::GetProcId()}, _signalManager{signalManager}, _errorStatistics{}, - _pUnwinder{pUnwinder} + _pUnwinder{pUnwinder}, + _tracer{nullptr} { if (_signalManager != nullptr) { @@ -59,6 +64,11 @@ LinuxStackFramesCollector::~LinuxStackFramesCollector() _errorStatistics.Log(); } +void LinuxStackFramesCollector::SetTracer(UnwinderTracer* tracer) +{ + _tracer = tracer; +} + bool LinuxStackFramesCollector::ShouldLogStats() { static std::time_t PreviousPrintTimestamp = 0; @@ -105,6 +115,9 @@ StackSnapshotResultBuffer* LinuxStackFramesCollector::CollectStackSampleImplemen // Otherwise, the CPU consumption to collect the callstack, will be accounted as "user app CPU time" auto timerId = pThreadInfo->GetTimerId(); +#ifdef ARM64 + auto tracer = std::make_unique(); +#endif if (selfCollect) { // In case we are self-unwinding, we do not want to be interrupted by the signal-based profilers (walltime and cpu) @@ -112,8 +125,12 @@ StackSnapshotResultBuffer* LinuxStackFramesCollector::CollectStackSampleImplemen // This lock is acquired by the signal-based profiler (see StackSamplerLoop->StackSamplerLoopManager) pThreadInfo->AcquireLock(); +#ifdef ARM64 + _tracer = tracer.get(); +#endif on_leave { + _tracer = nullptr; pThreadInfo->ReleaseLock(); }; @@ -132,8 +149,11 @@ StackSnapshotResultBuffer* LinuxStackFramesCollector::CollectStackSampleImplemen const auto threadId = static_cast<::pid_t>(pThreadInfo->GetOsThreadId()); s_pInstanceCurrentlyStackWalking = this; +#ifdef ARM64 + s_pInstanceCurrentlyStackWalking->SetTracer(tracer.get()); +#endif - on_leave { s_pInstanceCurrentlyStackWalking = nullptr; }; + on_leave { s_pInstanceCurrentlyStackWalking->SetTracer(nullptr); s_pInstanceCurrentlyStackWalking = nullptr; }; _stackWalkFinished = false; @@ -221,7 +241,14 @@ std::int32_t LinuxStackFramesCollector::CollectCallStackCurrentThread(void* ctx) inline std::int32_t LinuxStackFramesCollector::CollectStack(void* ctx) { auto buffer = Data(); - auto count = _pUnwinder->Unwind(ctx, reinterpret_cast(buffer.data()), buffer.size()); + std::uintptr_t stackBase = 0; + std::uintptr_t stackEnd = 0; + auto& threadInfo = ManagedThreadInfo::CurrentThreadInfo; + if (threadInfo) + { + std::tie(stackBase, stackEnd) = threadInfo->GetStackBounds(); + } + auto count = _pUnwinder->Unwind(ctx, reinterpret_cast(buffer.data()), buffer.size(), stackBase, stackEnd, _tracer); if (count == 0) { diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.h index a6b9bb902fe0..19cde24af27d 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.h @@ -26,6 +26,7 @@ class IConfiguration; class CallstackProvider; class DiscardMetrics; class IUnwinder; +class UnwinderTracer; class LinuxStackFramesCollector : public StackFramesCollectorBase { @@ -71,6 +72,7 @@ class LinuxStackFramesCollector : public StackFramesCollectorBase bool CanCollect(int32_t threadId, siginfo_t* info, void* ucontext) const; std::int32_t CollectStack(void* ctx); void MarkAsInterrupted(); + void SetTracer(UnwinderTracer* tracer); std::int32_t _lastStackWalkErrorCode; std::condition_variable _stackWalkInProgressWaiter; @@ -97,4 +99,5 @@ class LinuxStackFramesCollector : public StackFramesCollectorBase std::shared_ptr _discardMetrics; IUnwinder* _pUnwinder; + UnwinderTracer* _tracer; }; \ No newline at end of file diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/OsSpecificApi.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/OsSpecificApi.cpp index 8a4756024274..de93c77cd3f3 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/OsSpecificApi.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/OsSpecificApi.cpp @@ -25,7 +25,12 @@ #include "OpSysTools.h" #include "ScopeFinalizer.h" +#include "IUnwinder.h" +#ifdef ARM64 +#include "HybridUnwinder.h" +#else #include "Backtrace2Unwinder.h" +#endif #include "IConfiguration.h" #include "IThreadInfo.h" #include "LinuxStackFramesCollector.h" @@ -46,6 +51,18 @@ using namespace std::chrono_literals; // not change during the lifetime of the process. static auto ticks_per_second = sysconf(_SC_CLK_TCK); +static IUnwinder* s_pUnwinder = nullptr; + +void InitializeUnwinder(ManagedCodeCache* managedCodeCache) +{ +#ifdef ARM64 + static auto unwinder = std::make_unique(managedCodeCache); +#else + static auto unwinder = std::make_unique(); +#endif + s_pUnwinder = unwinder.get(); +} + std::pair GetLastErrorMessage() { DWORD errorCode = errno; @@ -64,9 +81,8 @@ std::unique_ptr CreateNewStackFramesCollectorInstance( CallstackProvider* callstackProvider, MetricsRegistry& metricsRegistry) { - static auto pUnwinder = std::make_unique(); return std::make_unique( - ProfilerSignalManager::Get(SIGUSR1), pConfiguration, callstackProvider, metricsRegistry, pUnwinder.get()); + ProfilerSignalManager::Get(SIGUSR1), pConfiguration, callstackProvider, metricsRegistry, s_pUnwinder); } // https://linux.die.net/man/5/proc diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.cpp index 1a7189481b37..eb6c64de230e 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.cpp @@ -254,8 +254,15 @@ bool TimerCreateCpuProfiler::Collect(void* ctx) return false; } + std::uintptr_t stackBase = 0; + std::uintptr_t stackEnd = 0; + if (threadInfo) + { + std::tie(stackBase, stackEnd) = threadInfo->GetStackBounds(); + } + auto buffer = rawCpuSample->Stack.AsSpan(); - auto count = _pUnwinder->Unwind(ctx, buffer.data(), buffer.size()); + auto count = _pUnwinder->Unwind(ctx, buffer.data(), buffer.size(), stackBase, stackEnd); rawCpuSample->Stack.SetCount(count); if (count == 0) diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.cpp new file mode 100644 index 000000000000..d4c040eadff8 --- /dev/null +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.cpp @@ -0,0 +1,102 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc. + +#include "UnwinderTracer.h" + +#include +#include + +static const char* EventTypeName(EventType t) +{ + switch (t) + { + case EventType::Start: return "Start"; + case EventType::InitCursor: return "InitCursor"; + case EventType::NativeFrame: return "NativeFrame"; + case EventType::ManagedTransition: return "ManagedTransition"; + case EventType::LibunwindStep: return "LibunwindStep"; + case EventType::FrameChainStep: return "FrameChainStep"; + case EventType::Finish: return "Finish"; + default: return "Unknown"; + } +} + +static const char* FinishReasonName(FinishReason r) +{ + switch (r) + { + case FinishReason::Success: return "Success"; + case FinishReason::BufferFull: return "BufferFull"; + case FinishReason::FailedGetContext: return "FailedGetContext"; + case FinishReason::FailedInitLocal2: return "FailedInitLocal2"; + case FinishReason::FailedGetReg: return "FailedGetReg"; + case FinishReason::FailedLibunwindStep: return "FailedLibunwindStep"; + case FinishReason::NoStackBounds: return "NoStackBounds"; + case FinishReason::InvalidFp: return "InvalidFp"; + case FinishReason::TooManyNativeFrames: return "TooManyNativeFrames"; + case FinishReason::InvalidIp: return "InvalidIp"; + default: return "Unknown"; + } +} + +void UnwinderTracer::WriteTo(std::ostream& os) const +{ + auto recorded = RecordedEvents(); + os << "# UnwinderTrace: " << recorded << " events recorded, " + << _totalEvents << " total"; + if (Overflowed()) + os << " (" << (_totalEvents - Capacity) << " discarded)"; + os << "\n"; + + for (std::size_t i = 0; i < recorded; ++i) + { + const auto& e = _entries[i]; + os << "[" << std::setw(3) << i << "] " + << std::left << std::setw(20) << EventTypeName(e.eventType); + + switch (e.eventType) + { + case EventType::Start: + os << " result=" << e.result; + break; + + case EventType::Finish: + os << " result=" << e.result + << " reason=" << FinishReasonName(e.finishReason); + break; + + case EventType::InitCursor: + case EventType::LibunwindStep: + { + const auto& cs = e.cursorSnapshot; + os << " result=" << e.result + << " cursor={ ip=0x" << std::hex << cs.ip + << " cfa=0x" << cs.cfa + << " locFp=0x" << cs.locFp + << " locLr=0x" << cs.locLr + << " locSp=0x" << cs.locSp + << std::dec + << " nextToSignalFrame=" << cs.nextToSignalFrame + << " cfaIsUnreliable=" << cs.cfaIsUnreliable + << " frameType=" << cs.frameType + << " cfaRegSp=" << cs.cfaRegSp + << " cfaRegOffset=" << cs.cfaRegOffset + << " }"; + break; + } + + case EventType::NativeFrame: + os << " ip=0x" << std::hex << e.ip + << " fp=0x" << e.fp + << " sp=0x" << e.sp << std::dec; + break; + + case EventType::ManagedTransition: + case EventType::FrameChainStep: + os << " ip=0x" << std::hex << e.ip + << " fp=0x" << e.fp << std::dec; + break; + } + os << "\n"; + } +} diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.h new file mode 100644 index 000000000000..e8138145f645 --- /dev/null +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/UnwinderTracer.h @@ -0,0 +1,265 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc. + +#pragma once + +#include +#include +#include +#include + +#define UNW_LOCAL_ONLY +#include + +// --------------------------------------------------------------------------- +// Mirror structs for libunwind internals (pinned to DataDog/libunwind v1.8.1-custom-3) +// +// These reproduce the memory layout of struct dwarf_cursor and struct cursor +// so we can extract diagnostic fields without including internal headers. +// static_asserts verify the layout hasn't drifted. +// +// IMPORTANT: libunwind is compiled WITHOUT UNW_LOCAL_ONLY, so dwarf_loc_t +// always has both val and type fields (16 bytes), even though consumer code +// defines UNW_LOCAL_ONLY which would make it 8 bytes. The mirror must match +// the library's internal layout, not the consumer-visible layout. +// --------------------------------------------------------------------------- + +namespace libunwind_mirror { + +static constexpr int kNumEhRegs = 4; // UNW_TDEP_NUM_EH_REGS +static constexpr int kNumPreservedRegs = 97; // DWARF_NUM_PRESERVED_REGS +static constexpr int kLocFp = 29; // UNW_AARCH64_X29 +static constexpr int kLocLr = 30; // UNW_AARCH64_X30 +static constexpr int kLocSp = 31; // UNW_AARCH64_SP + +// libunwind is built without UNW_LOCAL_ONLY → dwarf_loc_t has val + type +struct dwarf_loc +{ + std::uintptr_t val; + std::uintptr_t type; +}; + +struct dwarf_cursor +{ + void* as_arg; + void* as; + std::uintptr_t cfa; + std::uintptr_t ip; + std::uintptr_t args_size; + std::uintptr_t eh_args[kNumEhRegs]; + std::uint32_t eh_valid_mask; + // 4 bytes implicit padding to align loc[] to 8 + std::uint32_t _pad0; + dwarf_loc loc[kNumPreservedRegs]; + std::uint32_t bitfields; + // 4 bytes implicit padding to align pi to 8 + std::uint32_t _pad1; + unw_proc_info_t pi; + short hint; + short prev_rs; +}; + +// bitfield bit indices within dwarf_cursor::bitfields +static constexpr int kBitNextToSignalFrame = 4; +static constexpr int kBitCfaIsUnreliable = 5; + +struct tdep_frame +{ + std::uint64_t virtual_address; + std::int64_t frame_type : 2; + std::int64_t last_frame : 1; + std::int64_t cfa_reg_sp : 1; + std::int64_t cfa_reg_offset : 30; + std::int64_t fp_cfa_offset : 30; + std::int64_t lr_cfa_offset : 30; + std::int64_t sp_cfa_offset : 30; +}; + +struct cursor +{ + dwarf_cursor dwarf; + tdep_frame frame_info; +}; + +static_assert(sizeof(dwarf_loc) == 16, + "dwarf_loc must be 16 bytes (val + type) to match libunwind internal layout"); +static_assert(sizeof(dwarf_cursor) == 1720, + "dwarf_cursor size mismatch -- check dwarf_loc size and field layout"); +static_assert(sizeof(unw_cursor_t) == 250 * sizeof(unw_word_t), + "unw_cursor_t size changed -- update mirror structs"); +static_assert(sizeof(cursor) <= sizeof(unw_cursor_t), + "mirror cursor exceeds unw_cursor_t -- layout drift"); +static_assert(offsetof(dwarf_cursor, ip) == 24, + "dwarf_cursor::ip offset changed"); +static_assert(offsetof(dwarf_cursor, cfa) == 16, + "dwarf_cursor::cfa offset changed"); +static_assert(offsetof(dwarf_cursor, loc) == 80, + "dwarf_cursor::loc offset changed"); + +} // namespace libunwind_mirror + + +// --------------------------------------------------------------------------- +// EventType +// --------------------------------------------------------------------------- +enum class EventType : std::uint8_t +{ + Start = 0, + InitCursor = 1, + NativeFrame = 2, + ManagedTransition = 3, + LibunwindStep = 4, + FrameChainStep = 5, + Finish = 6, +}; + +// --------------------------------------------------------------------------- +// FinishReason -- why Unwind stopped +// --------------------------------------------------------------------------- +enum class FinishReason : std::uint8_t +{ + Success = 0, + BufferFull = 1, + FailedGetContext = 2, + FailedInitLocal2 = 3, + FailedGetReg = 4, + FailedLibunwindStep = 5, + NoStackBounds = 6, + InvalidFp = 7, + TooManyNativeFrames = 8, + InvalidIp = 9, +}; + +// --------------------------------------------------------------------------- +// CursorSnapshot -- 10 diagnostic fields from libunwind cursor internals +// --------------------------------------------------------------------------- +struct CursorSnapshot +{ + std::uintptr_t ip; + std::uintptr_t cfa; + std::uintptr_t locFp; + std::uintptr_t locLr; + std::uintptr_t locSp; + std::uint32_t nextToSignalFrame; + std::uint32_t cfaIsUnreliable; + + std::int64_t frameType; + std::int64_t cfaRegSp; + std::int64_t cfaRegOffset; +}; + +// --------------------------------------------------------------------------- +// TraceEvent +// --------------------------------------------------------------------------- +struct TraceEvent +{ + EventType eventType; + FinishReason finishReason; + std::int32_t result; + std::uintptr_t ip; + std::uintptr_t fp; + std::uintptr_t sp; + CursorSnapshot cursorSnapshot; +}; + +// --------------------------------------------------------------------------- +// SnapshotCursor -- extract CursorSnapshot from opaque unw_cursor_t +// --------------------------------------------------------------------------- +inline CursorSnapshot SnapshotCursor(const unw_cursor_t& opaque) +{ + using namespace libunwind_mirror; + const auto* c = reinterpret_cast(&opaque); + CursorSnapshot s; + s.ip = c->dwarf.ip; + s.cfa = c->dwarf.cfa; + s.locFp = c->dwarf.loc[kLocFp].val; + s.locLr = c->dwarf.loc[kLocLr].val; + s.locSp = c->dwarf.loc[kLocSp].val; + s.nextToSignalFrame = (c->dwarf.bitfields >> kBitNextToSignalFrame) & 1; + s.cfaIsUnreliable = (c->dwarf.bitfields >> kBitCfaIsUnreliable) & 1; + s.frameType = c->frame_info.frame_type; + s.cfaRegSp = c->frame_info.cfa_reg_sp; + s.cfaRegOffset = c->frame_info.cfa_reg_offset; + return s; +} + +// --------------------------------------------------------------------------- +// UnwinderTracer +// --------------------------------------------------------------------------- +class UnwinderTracer +{ +public: + void Reset() + { + _totalEvents = 0; + } + + void Record(EventType eventType, std::int32_t result = 0) + { + if (_totalEvents < Capacity) + { + _entries[_totalEvents].eventType = eventType; + _entries[_totalEvents].result = result; + } + _totalEvents++; + } + + void RecordFinish(std::int32_t result, FinishReason reason) + { + if (_totalEvents < Capacity) + { + _entries[_totalEvents].eventType = EventType::Finish; + _entries[_totalEvents].result = result; + _entries[_totalEvents].finishReason = reason; + } + _totalEvents++; + } + + void Record(EventType eventType, std::int32_t result, const unw_cursor_t& cursor) + { + if (_totalEvents < Capacity) + { + _entries[_totalEvents].eventType = eventType; + _entries[_totalEvents].result = result; + _entries[_totalEvents].cursorSnapshot = SnapshotCursor(cursor); + } + _totalEvents++; + } + + void Record(EventType eventType, std::uintptr_t ip, std::uintptr_t fp, std::uintptr_t sp) + { + if (_totalEvents < Capacity) + { + _entries[_totalEvents].eventType = eventType; + _entries[_totalEvents].ip = ip; + _entries[_totalEvents].fp = fp; + _entries[_totalEvents].sp = sp; + } + _totalEvents++; + } + + void Record(EventType eventType, std::uintptr_t ip, std::uintptr_t fp) + { + if (_totalEvents < Capacity) + { + _entries[_totalEvents].eventType = eventType; + _entries[_totalEvents].ip = ip; + _entries[_totalEvents].fp = fp; + } + _totalEvents++; + } + + std::size_t TotalEvents() const { return _totalEvents; } + std::size_t RecordedEvents() const { return std::min(_totalEvents, Capacity); } + bool Overflowed() const { return _totalEvents > Capacity; } + + const TraceEvent* begin() const { return _entries.data(); } + const TraceEvent* end() const { return _entries.data() + RecordedEvents(); } + + void WriteTo(std::ostream& os) const; + +private: + static constexpr std::size_t Capacity = 256; + std::array _entries; + std::size_t _totalEvents = 0; +}; diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Windows/OsSpecificApi.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Windows/OsSpecificApi.cpp index 8edb240ada9c..040c6b6971db 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Windows/OsSpecificApi.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native.Windows/OsSpecificApi.cpp @@ -32,6 +32,8 @@ class CallstackProvider; namespace OsSpecificApi { +void InitializeUnwinder(ManagedCodeCache*) {} + // if a system message was not found for the last error code the message will contain GetLastError between () std::pair GetLastErrorMessage() { diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/CorProfilerCallback.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/CorProfilerCallback.cpp index 95a0e48fb326..ac8d36ed2cbc 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/CorProfilerCallback.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/CorProfilerCallback.cpp @@ -56,12 +56,17 @@ #include "ThreadsCpuManager.h" #include "WallTimeProvider.h" #ifdef LINUX +#ifdef ARM64 +#include "HybridUnwinder.h" +#else #include "Backtrace2Unwinder.h" +#endif #include "ProfilerSignalManager.h" #include "SystemCallsShield.h" #include "TimerCreateCpuProfiler.h" #include "LibrariesInfoCache.h" #include "CpuSampleProvider.h" +#include #endif #include "shared/src/native-src/pal.h" @@ -620,7 +625,11 @@ void CorProfilerCallback::InitializeServices() #ifdef LINUX if (_pConfiguration->IsCpuProfilingEnabled() && _pConfiguration->GetCpuProfilerType() == CpuProfilerType::TimerCreate) { +#ifdef ARM64 + _pUnwinder = std::make_unique(_managedCodeCache.get()); +#else _pUnwinder = std::make_unique(); +#endif // Other alternative in case of crash-at-shutdown, do not register it as a service // we will have to start it by hand (already stopped by hand) _pCpuProfiler = std::make_unique( @@ -1546,6 +1555,8 @@ HRESULT STDMETHODCALLTYPE CorProfilerCallback::Initialize(IUnknown* corProfilerI } } + OsSpecificApi::InitializeUnwinder(_managedCodeCache.get()); + // create services without starting them InitializeServices(); @@ -2257,7 +2268,23 @@ HRESULT STDMETHODCALLTYPE CorProfilerCallback::ThreadAssignedToOSThread(ThreadID _pManagedThreadList->SetThreadOsInfo(managedThreadId, osThreadId, dupOsThreadHandle); #ifdef LINUX - // This call must be made *after* we assigne the SetThreadOsInfo function call. + { + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) == 0) + { + void* stackAddr; + size_t stackSize; + if (pthread_attr_getstack(&attr, &stackAddr, &stackSize) == 0) + { + auto stackBase = reinterpret_cast(stackAddr); + auto stackEnd = stackBase + stackSize; + threadInfo->SetStackBounds(stackBase, stackEnd); + } + pthread_attr_destroy(&attr); + } + } + + // This call must be made *after* we assign the SetThreadOsInfo function call. // Otherwise the threadInfo won't have it's OsThread field set and timer_create // will have random behavior. if (_pCpuProfiler != nullptr) @@ -2277,16 +2304,20 @@ HRESULT STDMETHODCALLTYPE CorProfilerCallback::ThreadAssignedToOSThread(ThreadID } // TL;DR prevent the profiler from deadlocking application thread on malloc - // Backtrace2Unwinder relies on libunwind. We need to call it to make sure + // The unwinder relies on libunwind. We need to call it to make sure // libunwind allocates and initializes TLS (Thread Local Storage) data structures for the current // thread. // Initialization of TLS object does call malloc. Unfortunately, if those calls to malloc // occurs in our profiler signal handler, we end up deadlocking the application. - // To prevent that, we call unw_backtrace here for the current thread, to force libunwind + // To prevent that, we call the unwinder here for the current thread, to force libunwind // initializing the TLS'd data structures for the current thread. - Backtrace2Unwinder bt2; +#ifdef ARM64 + HybridUnwinder warmup(_managedCodeCache.get()); +#else + Backtrace2Unwinder warmup; +#endif uintptr_t tab[1]; - bt2.Unwind(nullptr, tab, 1); + warmup.Unwind(nullptr, tab, 1); // check if SIGUSR1 signal is blocked for current thread sigset_t currentMask; diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DebugInfoStore.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DebugInfoStore.h index bdf63b294c3d..0df24a30014a 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DebugInfoStore.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DebugInfoStore.h @@ -71,7 +71,7 @@ class DebugInfoStore : public IDebugInfoStore public: DebugInfoStore(ICorProfilerInfo4* profilerInfo, IConfiguration* configuration) noexcept; - SymbolDebugInfo Get(ModuleID moduleId, mdMethodDef methodDef); + SymbolDebugInfo Get(ModuleID moduleId, mdMethodDef methodDef) override; // Memory measurement (IMemoryFootprintProvider) size_t GetMemorySize() const override; diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DllMain.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DllMain.cpp index f6b671d78767..655ea293fb81 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DllMain.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/DllMain.cpp @@ -140,11 +140,6 @@ extern "C" HRESULT STDMETHODCALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID r return CORPROF_E_PROFILER_CANCEL_ACTIVATION; } -#ifdef ARM64 - Log::Warn("Profiler is deactivated because it runs on an unsupported architecture."); - return CORPROF_E_PROFILER_CANCEL_ACTIVATION; -#endif - CorProfilerCallbackFactory* factory = new CorProfilerCallbackFactory(std::move(configuration)); if (factory == nullptr) { diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.cpp b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.cpp index f62af188dddb..cf78ce6df2a5 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.cpp +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.cpp @@ -69,9 +69,23 @@ bool ManagedCodeCache::Initialize() return false; } -bool ManagedCodeCache::IsCodeInR2RModule(std::uintptr_t ip) const noexcept +bool ManagedCodeCache::IsCodeInR2RModule(std::uintptr_t ip, bool signalSafe) const noexcept { - std::shared_lock moduleLock(_modulesMutex); + // IsCodeInR2RModule can be called in a signal handler or not. + // If it's called in a signal handler, we need to use a shared lock with try_to_lock. + // If it's called not in a signal handler, we need to use a shared lock with defer_lock. + auto moduleLock = [](std::shared_mutex& mutex, bool signalSafe) { + if (signalSafe) + { + return std::shared_lock(mutex, std::try_to_lock); + } + return std::shared_lock(mutex); + }(_modulesMutex, signalSafe); + + if (!moduleLock.owns_lock()) + { + return false; + } auto moduleCodeRange = FindRange(_modulesCodeRanges, ip); if (!moduleCodeRange.has_value()) @@ -81,7 +95,8 @@ bool ManagedCodeCache::IsCodeInR2RModule(std::uintptr_t ip) const noexcept if (moduleCodeRange->isRemoved) { - LogOnce(Debug, "ManagedCodeCache::IsCodeInR2RModule: Module code range was removed for ip: 0x", std::hex, ip); + // No print, can be called in a signal handler + // LogOnce(Debug, "ManagedCodeCache::IsCodeInR2RModule: Module code range was removed for ip: 0x", std::hex, ip); return false; } @@ -100,7 +115,7 @@ std::optional ManagedCodeCache::GetFunctionId(std::uintptr_t ip) noe // Level 2: Check if the IP is within a module code range - if (IsCodeInR2RModule(ip)) + if (IsCodeInR2RModule(ip, false)) { auto functionId = GetFunctionFromIP_Original(ip); if (functionId.has_value()) { @@ -179,25 +194,30 @@ bool ManagedCodeCache::IsManaged(std::uintptr_t ip) const noexcept { // Level 1: Find the page (shared lock on map structure) - std::shared_lock mapLock(_pagesMutex); - auto pageIt = _pagesMap.find(page); - if (pageIt == _pagesMap.end()) + std::shared_lock mapLock(_pagesMutex, std::try_to_lock); + if (!mapLock.owns_lock()) { - return false; // No code on this page + return false; } - - // Level 2: Binary search within the page's ranges (shared lock on page) - - std::shared_lock pageLock(pageIt->second.lock); - auto range = FindRange(pageIt->second.ranges, static_cast(ip)); - if (range.has_value()) + auto pageIt = _pagesMap.find(page); + if (pageIt != _pagesMap.end()) { - return true; + // Level 2: Binary search within the page's ranges (shared lock on page) + std::shared_lock pageLock(pageIt->second.lock, std::try_to_lock); + if (!pageLock.owns_lock()) + { + return false; + } + auto range = FindRange(pageIt->second.ranges, static_cast(ip)); + if (range.has_value()) + { + return true; + } } } - // Check if the IP is within a module code range - return IsCodeInR2RModule(ip); + // Page not found or IP not in any JIT-compiled range: check R2R modules + return IsCodeInR2RModule(ip, true); } void ManagedCodeCache::AddFunction(FunctionID functionId) diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.h index 5d6319ff24b0..72994f2e943d 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedCodeCache.h @@ -134,7 +134,13 @@ class ManagedCodeCache { // Append new ranges to the cache (accumulative - never removes old ranges) // This preserves old tier code that might still be on the stack void AddFunctionRangesToCache(std::vector newRanges); +#ifdef DD_TEST +public: +#endif void AddModuleRangesToCache(std::vector moduleCodeRanges); +#ifdef DD_TEST +private: +#endif void AddModuleCodeRangesAsync(std::vector moduleCodeRanges); void AddFunctionCodeRangesAsync(std::vector ranges); std::vector GetModuleCodeRanges(ModuleID moduleId); @@ -167,7 +173,7 @@ class ManagedCodeCache { template void EnqueueWork(WorkType work); std::optional GetFunctionIdImpl(std::uintptr_t ip) const noexcept; - bool IsCodeInR2RModule(std::uintptr_t ip) const noexcept; + bool IsCodeInR2RModule(std::uintptr_t ip, bool signalSafe) const noexcept; std::optional GetFunctionFromIP_Original(std::uintptr_t ip) noexcept; void AddFunctionImpl(FunctionID functionId, bool isAsync); diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedThreadInfo.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedThreadInfo.h index 34802e7f806c..c95c229a179e 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedThreadInfo.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/ManagedThreadInfo.h @@ -102,6 +102,8 @@ struct ManagedThreadInfo : public IThreadInfo inline int32_t SetTimerId(int32_t timerId); inline int32_t GetTimerId() const; inline bool CanBeInterrupted() const; + inline void SetStackBounds(std::uintptr_t stackBase, std::uintptr_t stackEnd); + inline std::pair GetStackBounds() const; #endif #ifdef DD_TEST @@ -170,6 +172,8 @@ struct ManagedThreadInfo : public IThreadInfo // doing a syscalls. volatile int* _sharedMemoryArea; std::int32_t _timerId; + std::uintptr_t _stackBase = 0; + std::uintptr_t _stackEnd = 0; #endif uint64_t _blockingThreadId; shared::WSTRING _blockingThreadName; @@ -430,6 +434,17 @@ inline std::int32_t ManagedThreadInfo::GetTimerId() const { return _timerId; } + +inline void ManagedThreadInfo::SetStackBounds(std::uintptr_t stackBase, std::uintptr_t stackEnd) +{ + _stackBase = stackBase; + _stackEnd = stackEnd; +} + +inline std::pair ManagedThreadInfo::GetStackBounds() const +{ + return {_stackBase, _stackEnd}; +} #endif inline AppDomainID ManagedThreadInfo::GetAppDomainId() diff --git a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/OsSpecificApi.h b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/OsSpecificApi.h index 59ddfb58d70e..ab14de35d833 100644 --- a/profiler/src/ProfilerEngine/Datadog.Profiler.Native/OsSpecificApi.h +++ b/profiler/src/ProfilerEngine/Datadog.Profiler.Native/OsSpecificApi.h @@ -22,11 +22,14 @@ class IAllocationsListener; class IContentionListener; class IGCSuspensionsListener; class CallstackProvider; +class ManagedCodeCache; // Those functions must be defined in the main projects (Linux and Windows) // Here are forward declarations to avoid hard coupling namespace OsSpecificApi { + void InitializeUnwinder(ManagedCodeCache* managedCodeCache); + std::unique_ptr CreateNewStackFramesCollectorInstance( ICorProfilerInfo4* pCorProfilerInfo, IConfiguration const* pConfiguration, diff --git a/profiler/test/Datadog.Profiler.IntegrationTests/Exceptions/ExceptionsTest.cs b/profiler/test/Datadog.Profiler.IntegrationTests/Exceptions/ExceptionsTest.cs index 37a92838a13a..af01c9bb0a65 100644 --- a/profiler/test/Datadog.Profiler.IntegrationTests/Exceptions/ExceptionsTest.cs +++ b/profiler/test/Datadog.Profiler.IntegrationTests/Exceptions/ExceptionsTest.cs @@ -113,7 +113,9 @@ public void ThrowExceptionsInParallel(string appName, string framework, string a total += sample.Count; sample.Type.Should().Be("System.Exception"); sample.Message.Should().BeEmpty(); - Assert.True(sample.Stacktrace.EndWith(expectedStack)); + Assert.True( + sample.Stacktrace.EndWith(expectedStack), + $"Stacktrace does not end with expected frames.\nExpected ({expectedStack.FramesCount} frames):\n{expectedStack}\nActual ({sample.Stacktrace.FramesCount} frames):\n{sample.Stacktrace}"); } foreach (var file in Directory.GetFiles(runner.Environment.LogDir)) @@ -221,7 +223,9 @@ public void ThrowExceptionsInParallelWithCustomGetFunctionFromIp(string appName, total += sample.Count; sample.Type.Should().Be("System.Exception"); sample.Message.Should().BeEmpty(); - Assert.True(sample.Stacktrace.EndWith(expectedStack)); + Assert.True( + sample.Stacktrace.EndWith(expectedStack), + $"Stacktrace does not end with expected frames.\nExpected ({expectedStack.FramesCount} frames):\n{expectedStack}\nActual ({sample.Stacktrace.FramesCount} frames):\n{sample.Stacktrace}"); } foreach (var file in Directory.GetFiles(runner.Environment.LogDir)) @@ -295,7 +299,9 @@ public void ThrowExceptionsInParallelWithNewCpuProfiler(string appName, string f { labels.Should().ContainSingle(x => x.Name == "exception type" && x.Value == "System.Exception"); labels.Should().ContainSingle(x => x.Name == "exception message" && string.IsNullOrWhiteSpace(x.Value)); - Assert.True(stackTrace.EndWith(expectedStack)); + Assert.True( + stackTrace.EndWith(expectedStack), + $"Stacktrace does not end with expected frames.\nExpected ({expectedStack.FramesCount} frames):\n{expectedStack}\nActual ({stackTrace.FramesCount} frames):\n{stackTrace}"); } foreach (var file in Directory.GetFiles(runner.Environment.LogDir)) diff --git a/profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentHelper.cs b/profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentHelper.cs index a46ee4b2cee4..96f709ecffd4 100644 --- a/profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentHelper.cs +++ b/profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentHelper.cs @@ -81,7 +81,12 @@ public static bool IsRunningOnWindows() public static string GetPlatform() { - return Environment.Is64BitProcess ? "x64" : "x86"; + return RuntimeInformation.ProcessArchitecture switch + { + Architecture.Arm64 => "ARM64", + Architecture.X86 => "x86", + _ => "x64", + }; } public static bool IsRunningInCi() => @@ -352,8 +357,8 @@ private static string GetArchitectureSubfolder(bool isAlpine) ("win", "x86", _) => "win-x86", ("linux", "x64", false) => "linux-x64", ("linux", "x64", true) => "linux-musl-x64", - ("linux", "Arm64", false) => "linux-arm64", - ("linux", "Arm64", true) => "linux-musl-arm64", + ("linux", "ARM64", false) => "linux-arm64", + ("linux", "ARM64", true) => "linux-musl-arm64", ("osx", _, _) => "osx-x64", _ => throw new PlatformNotSupportedException() }; diff --git a/profiler/test/Datadog.Profiler.Native.Tests/LibrariesInfoCacheTest.cpp b/profiler/test/Datadog.Profiler.Native.Tests/LibrariesInfoCacheTest.cpp index 0dce96bd9881..73fd948a3191 100644 --- a/profiler/test/Datadog.Profiler.Native.Tests/LibrariesInfoCacheTest.cpp +++ b/profiler/test/Datadog.Profiler.Native.Tests/LibrariesInfoCacheTest.cpp @@ -6,7 +6,9 @@ #include "MemoryResourceManager.h" #include "LibrariesInfoCache.h" +#ifndef ARM64 #include "Backtrace2Unwinder.h" +#endif struct ServiceWrapper { diff --git a/profiler/test/Datadog.Profiler.Native.Tests/LinuxStackFramesCollectorTest.cpp b/profiler/test/Datadog.Profiler.Native.Tests/LinuxStackFramesCollectorTest.cpp index 7569e1b19f88..d00045846105 100644 --- a/profiler/test/Datadog.Profiler.Native.Tests/LinuxStackFramesCollectorTest.cpp +++ b/profiler/test/Datadog.Profiler.Native.Tests/LinuxStackFramesCollectorTest.cpp @@ -7,7 +7,12 @@ #include "profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/LinuxStackFramesCollector.h" #include "profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/ProfilerSignalManager.h" +#ifdef ARM64 +#include "HybridUnwinder.h" +#include "ManagedCodeCache.h" +#else #include "Backtrace2Unwinder.h" +#endif #include "CallstackProvider.h" #include "ManagedThreadInfo.h" #include "MemoryResourceManager.h" @@ -159,7 +164,13 @@ class LinuxStackFramesCollectorFixture : public ::testing::Test _stopWorker = false; _workerThread = std::make_unique(_stopWorker); +#ifdef ARM64 + // TODO maybe a mock of ICorProfilerInfo to avoid crashing + _pManagedCodeCache = std::make_unique(nullptr); + _pUnwinder = std::make_unique(_pManagedCodeCache.get()); +#else _pUnwinder = std::make_unique(); +#endif ResetCallbackState(); @@ -326,6 +337,9 @@ class LinuxStackFramesCollectorFixture : public ::testing::Test std::future _callbackCalledFuture; std::unique_ptr _workerThread; std::unique_ptr _librariesInfoCache; +#ifdef ARM64 + std::unique_ptr _pManagedCodeCache; +#endif std::unique_ptr _pUnwinder; }; diff --git a/profiler/test/Datadog.Profiler.Native.Tests/ManagedCodeCacheTest.cpp b/profiler/test/Datadog.Profiler.Native.Tests/ManagedCodeCacheTest.cpp index 4c24fe484c5b..e9774f1866f2 100644 --- a/profiler/test/Datadog.Profiler.Native.Tests/ManagedCodeCacheTest.cpp +++ b/profiler/test/Datadog.Profiler.Native.Tests/ManagedCodeCacheTest.cpp @@ -283,6 +283,27 @@ TEST_F(ManagedCodeCacheTest, GetFunctionId_NullIP_ReturnsEmpty) { EXPECT_FALSE(cache->GetFunctionId(0).has_value()); } +// Test: IsManaged falls back to R2R module check when IP is not in the JIT page map +TEST_F(ManagedCodeCacheTest, IsManaged_IPInR2RModule_NotInPageMap_ReturnsTrue) { + // Register an R2R module range directly (bypassing PE parsing) + // Use an address that has no JIT-compiled code registered on its page + uintptr_t r2rCodeStart = 0xA0000000; + uintptr_t r2rCodeEnd = 0xA000FFFF; + + std::vector moduleRanges; + moduleRanges.emplace_back(r2rCodeStart, r2rCodeEnd); + + cache->AddModuleRangesToCache(std::move(moduleRanges)); + + // An IP within the R2R range but with no JIT page entry should still be detected as managed + uintptr_t ipInR2R = r2rCodeStart + 0x500; + EXPECT_TRUE(cache->IsManaged(ipInR2R)) + << "IsManaged should return true for an IP in an R2R module even when the JIT page map has no entry for that page"; + + // An IP outside both the JIT page map and any R2R module should still be false + EXPECT_FALSE(cache->IsManaged(0xDEADBEEF)); +} + // Test: GetCodeInfo2 failure handling TEST_F(ManagedCodeCacheTest, AddFunction_GetCodeInfo2Fails_HandledGracefully) { FunctionID testFuncId = 999; diff --git a/shared/src/Datadog.Trace.ClrProfiler.Native/loader.conf b/shared/src/Datadog.Trace.ClrProfiler.Native/loader.conf index 76571ddd520d..f4f53bc047fe 100644 --- a/shared/src/Datadog.Trace.ClrProfiler.Native/loader.conf +++ b/shared/src/Datadog.Trace.ClrProfiler.Native/loader.conf @@ -3,8 +3,8 @@ PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};win-x64;.\Datadog.Profiler.Nativ PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};win-x86;.\Datadog.Profiler.Native.dll PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-x64;./Datadog.Profiler.Native.so PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-musl-x64;./Datadog.Profiler.Native.so -# PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-arm64;./Datadog.Profiler.Native.so -# PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-musl-arm64;./Datadog.Profiler.Native.so +PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-arm64;./Datadog.Profiler.Native.so +PROFILER;{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A};linux-musl-arm64;./Datadog.Profiler.Native.so #Tracer TRACER;{50DA5EED-F1ED-B00B-1055-5AFE55A1ADE5};win-arm64;.\Datadog.Tracer.Native.dll diff --git a/tracer/build/_build/Build.Profiler.Steps.cs b/tracer/build/_build/Build.Profiler.Steps.cs index 61a6c9b7917a..651c72ea9872 100644 --- a/tracer/build/_build/Build.Profiler.Steps.cs +++ b/tracer/build/_build/Build.Profiler.Steps.cs @@ -329,7 +329,7 @@ partial class Build Target BuildAndRunProfilerCpuLimitTests => _ => _ .After(BuildProfilerSamples) .Description("Run the profiler container tests") - .Requires(() => IsLinux && !IsArm64) + .Requires(() => IsLinux) .Executes(() => { BuildAndRunProfilerIntegrationTestsInternal("(Category=CpuLimitTest)"); @@ -338,7 +338,6 @@ partial class Build Target BuildAndRunProfilerIntegrationTests => _ => _ .After(BuildProfilerSamples) .Description("Builds and runs the profiler integration tests") - .Requires(() => !IsArm64) .Executes(() => { // Exclude CpuLimitTest from this path: They are already launched in a specific step + specific setup diff --git a/tracer/build/_build/Build.Steps.cs b/tracer/build/_build/Build.Steps.cs index 7da2eb9e5180..4ea77a0c5ada 100644 --- a/tracer/build/_build/Build.Steps.cs +++ b/tracer/build/_build/Build.Steps.cs @@ -2578,12 +2578,6 @@ string NormalizedPath(AbsolutePath ap) knownPatterns.Add(new(@".*'dddlopen' dddlerror returned: Library linux-vdso\.so\.1 is not already loaded", RegexOptions.Compiled)); } - if (IsArm64) - { - // Profiler is not yet supported on Arm64 - knownPatterns.Add(new(@".*Profiler is deactivated because it runs on an unsupported architecture", RegexOptions.Compiled)); - } - var isAzureFunctionsScenario = SmokeTestCategory is SmokeTests.SmokeTestCategory.LinuxAzureFunctionsNuGet or SmokeTests.SmokeTestCategory.WindowsAzureFunctionsNuGet; if (isAzureFunctionsScenario) { diff --git a/tracer/build/_build/LogParsing/LogParser.cs b/tracer/build/_build/LogParsing/LogParser.cs index db094d5a6c03..f0b3a2e44e80 100644 --- a/tracer/build/_build/LogParsing/LogParser.cs +++ b/tracer/build/_build/LogParsing/LogParser.cs @@ -76,7 +76,7 @@ public static async Task DoLogsContainErrors( || (managedFiles.Count > 0 // && libdatadogFiles.Count > 0 Libdatadog exporter is off by default, so we don't require it to be there && nativeTracerFiles.Count > 0 - && (nativeProfilerFiles.Count > 0 || EnvironmentInfo.IsOsx || EnvironmentInfo.IsArm64) // profiler doesn't support mac or ARM64 + && (nativeProfilerFiles.Count > 0 || EnvironmentInfo.IsOsx) // profiler doesn't support mac && nativeLoaderFiles.Count > 0); var hasErrors = managedErrors.Count != 0 || libdatadogErrors.Count != 0 diff --git a/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerAvailabilityHelper.cs b/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerAvailabilityHelper.cs index df20016d2a98..d09fe8cf7741 100644 --- a/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerAvailabilityHelper.cs +++ b/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerAvailabilityHelper.cs @@ -33,7 +33,7 @@ internal static bool IsContinuousProfilerAvailable_TestingOnly(Func isClrP private static bool GetIsContinuousProfilerAvailable(Func isClrProfilerAttached) { - // Profiler is not available on ARM(64) + // Profiler is not available on ARM (32-bit) var fd = FrameworkDescription.Instance; if (!IsSupportedArch(fd)) { @@ -62,7 +62,7 @@ static bool IsSupportedArch(FrameworkDescription fd) return fd.OSPlatform switch { OSPlatformName.Windows when fd.ProcessArchitecture is ProcessArchitecture.X64 or ProcessArchitecture.X86 => true, - OSPlatformName.Linux when fd.ProcessArchitecture is ProcessArchitecture.X64 => true, + OSPlatformName.Linux when fd.ProcessArchitecture is ProcessArchitecture.X64 or ProcessArchitecture.Arm64 => true, _ => false, }; } diff --git a/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerSettings.cs b/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerSettings.cs index d12d964af9f3..49740ca40e36 100644 --- a/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerSettings.cs +++ b/tracer/src/Datadog.Trace/ContinuousProfiler/ProfilerSettings.cs @@ -86,7 +86,7 @@ public static bool IsProfilingSupported var fd = FrameworkDescription.Instance; return (fd.OSPlatform == OSPlatformName.Windows && fd.ProcessArchitecture is ProcessArchitecture.X64 or ProcessArchitecture.X86) || - (fd.OSPlatform == OSPlatformName.Linux && fd.ProcessArchitecture is ProcessArchitecture.X64); + (fd.OSPlatform == OSPlatformName.Linux && fd.ProcessArchitecture is ProcessArchitecture.X64 or ProcessArchitecture.Arm64); } } diff --git a/tracer/src/Datadog.Trace/NativeLoader.cs b/tracer/src/Datadog.Trace/NativeLoader.cs index 39bdb8056212..9d14a3aa9335 100644 --- a/tracer/src/Datadog.Trace/NativeLoader.cs +++ b/tracer/src/Datadog.Trace/NativeLoader.cs @@ -19,7 +19,7 @@ private static bool IsAvailable get { var fd = FrameworkDescription.Instance; - return fd.ProcessArchitecture != ProcessArchitecture.Arm && fd.ProcessArchitecture != ProcessArchitecture.Arm64; + return fd.ProcessArchitecture != ProcessArchitecture.Arm; } } diff --git a/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerAvailabilityHelperTests.cs b/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerAvailabilityHelperTests.cs index a98633124a32..aa33fcd63f28 100644 --- a/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerAvailabilityHelperTests.cs +++ b/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerAvailabilityHelperTests.cs @@ -33,6 +33,7 @@ public void IsContinuousProfilerAvailable_OnUnsupportedPlatforms_ReturnsFalse() SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Windows, SkipOn.ArchitectureValue.X64); SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Windows, SkipOn.ArchitectureValue.X86); SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Linux, SkipOn.ArchitectureValue.X64); + SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Linux, SkipOn.ArchitectureValue.ARM64); ProfilerAvailabilityHelper.IsContinuousProfilerAvailable_TestingOnly(ClrProfilerIsAttached).Should().BeFalse(); } @@ -90,7 +91,6 @@ private static void SkipUnsupported() { SkipOn.Platform(SkipOn.PlatformValue.MacOs); SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Linux, SkipOn.ArchitectureValue.X86); - SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Linux, SkipOn.ArchitectureValue.ARM64); SkipOn.PlatformAndArchitecture(SkipOn.PlatformValue.Windows, SkipOn.ArchitectureValue.ARM64); SkipOn.Platform(SkipOn.PlatformValue.Windows); // Windows is controlled by env var only, so doesn't apply to most tests } diff --git a/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerSettingsTests.cs b/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerSettingsTests.cs index b8dac79c257b..abf9332bc72e 100644 --- a/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerSettingsTests.cs +++ b/tracer/test/Datadog.Trace.Tests/ContinuousProfiler/ProfilerSettingsTests.cs @@ -147,6 +147,7 @@ public void ProfilerState_IsProfilingSupported_OnlySupportedOnExpectedPlatforms( (Architecture.X64, true, _) => true, // Windows x64 (Architecture.X86, true, _) => true, // Windows x86 (Architecture.X64, _, true) => true, // Linux x64 + (Architecture.Arm64, _, true) => true, // Linux arm64 _ => false // Unsupported platforms };