Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,30 @@ jobs:
needs: run-test
if: failure()
steps:
- name: Download failed tests artifact
- name: Download all failure artifacts
uses: actions/download-artifact@v4
with:
name: failures
pattern: failures-*
path: ./artifacts
merge-multiple: true
- name: Report failures
run: |
find ./artifacts -name 'failures_*' -exec cat {} \; > ./artifacts/failures.txt
scenarios=$(cat ./artifacts/failures.txt | tr '\n' ',')

# Check if any failure files exist
if ! ls ./artifacts/failures_*.txt 1> /dev/null 2>&1; then
echo "No failure artifacts found"
exit 0
fi

# Combine all failure files
find ./artifacts -name 'failures_*.txt' -exec cat {} \; > ./artifacts/all_failures.txt
scenarios=$(cat ./artifacts/all_failures.txt | tr '\n' ',' | sed 's/,$//')

echo "Failed scenarios: $scenarios"

curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-Type: application/json' \
-d "{'scenarios': '${scenarios}', 'failed_run_url': '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'}"
if [ -n "${{ secrets.SLACK_WEBHOOK }}" ]; then
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-Type: application/json' \
-d "{\"scenarios\": \"${scenarios}\", \"failed_run_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}"
else
echo "SLACK_WEBHOOK not configured, skipping notification"
fi
32 changes: 32 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ Never use 'gradle' or 'gradlew' directly. Instead, use the '/build-and-summarize
JAVA_TEST_HOME=/path/to/test/jdk ./gradlew testDebug
```

### Docker-based Testing (Recommended for ASan/Non-Local Environments)

**When to use**: For ASan testing, cross-architecture testing (aarch64), different libc variants (musl), or reproducing CI environment issues.

```bash
# ASan tests on aarch64 Linux
./utils/run-docker-tests.sh --arch=aarch64 --config=asan --libc=glibc --jdk=21

# Run specific test pattern
./utils/run-docker-tests.sh --arch=aarch64 --tests="*SpecificTest*"

# Enable C++ gtests
./utils/run-docker-tests.sh --arch=aarch64 --gtest

# Drop to shell for debugging
./utils/run-docker-tests.sh --arch=aarch64 --shell

# Test with musl libc
./utils/run-docker-tests.sh --libc=musl --jdk=21

# Test with OpenJ9
./utils/run-docker-tests.sh --jdk=21-j9

# Use mounted repo (faster, but may have stale artifacts)
./utils/run-docker-tests.sh --mount

# Rebuild Docker images
./utils/run-docker-tests.sh --rebuild
```

**Note**: The Docker script supports `--config=debug|release|asan|tsan`. Use this for cross-architecture testing and reproducing CI environments. For local development, use `./gradlew testAsan` directly.

### Build Options
```bash
# Skip native compilation
Expand Down
64 changes: 58 additions & 6 deletions ddprof-lib/src/main/cpp/stackWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

const uintptr_t MAX_WALK_SIZE = 0x100000;
const intptr_t MAX_INTERPRETER_FRAME_SIZE = 0x1000;
const intptr_t MAX_FRAME_SIZE_WORDS = StackWalkValidation::MAX_FRAME_SIZE / sizeof(void*); // 0x8000 = 32768 words

static ucontext_t empty_ucontext{};

Expand Down Expand Up @@ -187,11 +188,19 @@ int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth
pc = (const char*)pc + (f.fp_off >> 1);
} else {
if (f.fp_off != DW_SAME_FP && f.fp_off < MAX_FRAME_SIZE && f.fp_off > -MAX_FRAME_SIZE) {
fp = (uintptr_t)SafeAccess::load((void**)(sp + f.fp_off));
uintptr_t fp_addr = sp + f.fp_off;
if (!aligned(fp_addr)) {
break;
}
fp = (uintptr_t)SafeAccess::load((void**)fp_addr);
}

if (EMPTY_FRAME_SIZE > 0 || f.pc_off != DW_LINK_REGISTER) {
pc = stripPointer(SafeAccess::load((void**)(sp + f.pc_off)));
uintptr_t pc_addr = sp + f.pc_off;
if (!aligned(pc_addr)) {
break;
}
pc = stripPointer(SafeAccess::load((void**)pc_addr));
} else if (depth == 1) {
pc = (const void*)frame.link();
} else {
Expand Down Expand Up @@ -245,6 +254,10 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,

const void* pc = anchor->lastJavaPC();
if (pc == NULL) {
// Verify alignment before dereferencing sp as pointer
if (!aligned(sp)) {
return 0;
}
pc = ((const void**)sp)[-1];
}

Expand Down Expand Up @@ -334,6 +347,12 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
}

if (nm->isNMethod()) {
// Check if deoptimization is in progress before walking compiled frames
if (vm_thread != NULL && vm_thread->inDeopt()) {
fillFrame(frames[depth++], BCI_ERROR, "break_deopt_compiled");
break;
}

int level = nm->level();
FrameTypeId type = details && level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED;
fillFrame(frames[depth++], type, 0, nm->method()->id());
Expand All @@ -360,7 +379,21 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
// Handle situations when sp is temporarily changed in the compiled code
frame.adjustSP(nm->entry(), pc, sp);

sp += nm->frameSize() * sizeof(void*);
// Validate NMethod metadata before using frameSize()
int frame_size = nm->frameSize();
if (frame_size <= 0 || frame_size > MAX_FRAME_SIZE_WORDS) {
fillFrame(frames[depth++], BCI_ERROR, "break_invalid_framesize");
break;
}

sp += frame_size * sizeof(void*);

// Verify alignment before dereferencing sp as pointer (secondary defense)
if (!aligned(sp)) {
fillFrame(frames[depth++], BCI_ERROR, "break_misaligned_sp");
break;
}

fp = ((uintptr_t*)sp)[-FRAME_PC_SLOT - 1];
pc = ((const void**)sp)[-FRAME_PC_SLOT];
continue;
Expand Down Expand Up @@ -407,7 +440,7 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
sp = frame.senderSP();
fp = *(uintptr_t*)fp;
} else {
pc = stripPointer(*(void**)sp);
pc = stripPointer(SafeAccess::load((void**)sp));
sp = frame.senderSP();
}
continue;
Expand Down Expand Up @@ -455,7 +488,21 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
}

if (depth > 1 && nm->frameSize() > 0) {
sp += nm->frameSize() * sizeof(void*);
// Validate NMethod metadata before using frameSize()
int frame_size = nm->frameSize();
if (frame_size <= 0 || frame_size > MAX_FRAME_SIZE_WORDS) {
fillFrame(frames[depth++], BCI_ERROR, "break_invalid_framesize");
break;
}

sp += frame_size * sizeof(void*);

// Verify alignment before dereferencing sp as pointer (secondary defense)
if (!aligned(sp)) {
fillFrame(frames[depth++], BCI_ERROR, "break_misaligned_sp");
break;
}

fp = ((uintptr_t*)sp)[-FRAME_PC_SLOT - 1];
pc = ((const void**)sp)[-FRAME_PC_SLOT];
continue;
Expand Down Expand Up @@ -572,7 +619,12 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
}

if (EMPTY_FRAME_SIZE > 0 || f.pc_off != DW_LINK_REGISTER) {
pc = stripPointer(*(void**)(sp + f.pc_off));
// Verify alignment before dereferencing sp + offset
uintptr_t pc_addr = sp + f.pc_off;
if (!aligned(pc_addr)) {
break;
}
pc = stripPointer(SafeAccess::load((void**)pc_addr));
} else if (depth == 1) {
pc = (const void*)frame.link();
} else {
Expand Down
14 changes: 8 additions & 6 deletions utils/run-docker-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# --libc=glibc|musl (default: glibc)
# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9 (default: 21)
# --arch=x64|aarch64 (default: auto-detect)
# --config=debug|release (default: debug)
# --config=debug|release|asan|tsan (default: debug)
# --tests="TestPattern" (optional, specific test to run)
# --gtest (enable C++ gtests, disabled by default)
# --shell (drop to shell instead of running tests)
Expand Down Expand Up @@ -186,8 +186,8 @@ if [[ "$ARCH" != "x64" && "$ARCH" != "aarch64" ]]; then
exit 1
fi

if [[ "$CONFIG" != "debug" && "$CONFIG" != "release" ]]; then
echo "Error: --config must be 'debug' or 'release'"
if [[ "$CONFIG" != "debug" && "$CONFIG" != "release" && "$CONFIG" != "asan" && "$CONFIG" != "tsan" ]]; then
echo "Error: --config must be 'debug', 'release', 'asan', or 'tsan'"
exit 1
fi

Expand Down Expand Up @@ -291,14 +291,14 @@ ENV DEBIAN_FRONTEND=noninteractive

# Install build dependencies
# - libasan/libtsan for GCC sanitizers
# - libclang-rt-dev for clang sanitizers and libFuzzer
# - clang provides sanitizer runtimes and libFuzzer
# - openssh-client for git clone over SSH
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl wget bash make g++ clang git jq cmake \
libgtest-dev libgmock-dev tar binutils libc6-dbg \
ca-certificates linux-libc-dev \
libasan6 libtsan0 libclang-rt-dev openssh-client && \
libasan6 libtsan0 openssh-client && \
rm -rf /var/lib/apt/lists/*

# Set up Gradle cache directory
Expand Down Expand Up @@ -360,7 +360,9 @@ fi
# ========== Run Tests ==========

# Build gradle test command
GRADLE_CMD="./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG}"
# Capitalize first letter for gradle task names (testDebug, testAsan, etc.)
CONFIG_CAPITALIZED="$(tr '[:lower:]' '[:upper:]' <<< ${CONFIG:0:1})${CONFIG:1}"
GRADLE_CMD="./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG_CAPITALIZED}"
if [[ -n "$TESTS" ]]; then
GRADLE_CMD="$GRADLE_CMD --tests \"$TESTS\""
fi
Expand Down
Loading