Skip to content

Commit f71ea60

Browse files
authored
Fix stack walking alignment issues causing ASAN violations (#362)
1 parent 1392de7 commit f71ea60

File tree

4 files changed

+118
-20
lines changed

4 files changed

+118
-20
lines changed

.github/workflows/nightly.yml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,30 @@ jobs:
2020
needs: run-test
2121
if: failure()
2222
steps:
23-
- name: Download failed tests artifact
23+
- name: Download all failure artifacts
2424
uses: actions/download-artifact@v4
2525
with:
26-
name: failures
26+
pattern: failures-*
2727
path: ./artifacts
28+
merge-multiple: true
2829
- name: Report failures
2930
run: |
30-
find ./artifacts -name 'failures_*' -exec cat {} \; > ./artifacts/failures.txt
31-
scenarios=$(cat ./artifacts/failures.txt | tr '\n' ',')
32-
31+
# Check if any failure files exist
32+
if ! ls ./artifacts/failures_*.txt 1> /dev/null 2>&1; then
33+
echo "No failure artifacts found"
34+
exit 0
35+
fi
36+
37+
# Combine all failure files
38+
find ./artifacts -name 'failures_*.txt' -exec cat {} \; > ./artifacts/all_failures.txt
39+
scenarios=$(cat ./artifacts/all_failures.txt | tr '\n' ',' | sed 's/,$//')
40+
3341
echo "Failed scenarios: $scenarios"
3442
35-
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
36-
-H 'Content-Type: application/json' \
37-
-d "{'scenarios': '${scenarios}', 'failed_run_url': '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'}"
43+
if [ -n "${{ secrets.SLACK_WEBHOOK }}" ]; then
44+
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
45+
-H 'Content-Type: application/json' \
46+
-d "{\"scenarios\": \"${scenarios}\", \"failed_run_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}"
47+
else
48+
echo "SLACK_WEBHOOK not configured, skipping notification"
49+
fi

AGENTS.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,38 @@ Release builds automatically extract debug symbols via `NativeLinkTask`, reducin
167167

168168
**See:** `build-logic/README.md` for full documentation
169169

170+
### Docker-based Testing (Recommended for ASan/Non-Local Environments)
171+
172+
**When to use**: For ASan testing, cross-architecture testing (aarch64), different libc variants (musl), or reproducing CI environment issues.
173+
174+
```bash
175+
# ASan tests on aarch64 Linux
176+
./utils/run-docker-tests.sh --arch=aarch64 --config=asan --libc=glibc --jdk=21
177+
178+
# Run specific test pattern
179+
./utils/run-docker-tests.sh --arch=aarch64 --tests="*SpecificTest*"
180+
181+
# Enable C++ gtests
182+
./utils/run-docker-tests.sh --arch=aarch64 --gtest
183+
184+
# Drop to shell for debugging
185+
./utils/run-docker-tests.sh --arch=aarch64 --shell
186+
187+
# Test with musl libc
188+
./utils/run-docker-tests.sh --libc=musl --jdk=21
189+
190+
# Test with OpenJ9
191+
./utils/run-docker-tests.sh --jdk=21-j9
192+
193+
# Use mounted repo (faster, but may have stale artifacts)
194+
./utils/run-docker-tests.sh --mount
195+
196+
# Rebuild Docker images
197+
./utils/run-docker-tests.sh --rebuild
198+
```
199+
200+
**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.
201+
170202
### Build Options
171203
```bash
172204
# Skip native compilation

ddprof-lib/src/main/cpp/stackWalker.cpp

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

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

2122
static ucontext_t empty_ucontext{};
2223

@@ -187,11 +188,19 @@ int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth
187188
pc = (const char*)pc + (f.fp_off >> 1);
188189
} else {
189190
if (f.fp_off != DW_SAME_FP && f.fp_off < MAX_FRAME_SIZE && f.fp_off > -MAX_FRAME_SIZE) {
190-
fp = (uintptr_t)SafeAccess::load((void**)(sp + f.fp_off));
191+
uintptr_t fp_addr = sp + f.fp_off;
192+
if (!aligned(fp_addr)) {
193+
break;
194+
}
195+
fp = (uintptr_t)SafeAccess::load((void**)fp_addr);
191196
}
192197

193198
if (EMPTY_FRAME_SIZE > 0 || f.pc_off != DW_LINK_REGISTER) {
194-
pc = stripPointer(SafeAccess::load((void**)(sp + f.pc_off)));
199+
uintptr_t pc_addr = sp + f.pc_off;
200+
if (!aligned(pc_addr)) {
201+
break;
202+
}
203+
pc = stripPointer(SafeAccess::load((void**)pc_addr));
195204
} else if (depth == 1) {
196205
pc = (const void*)frame.link();
197206
} else {
@@ -245,6 +254,10 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
245254

246255
const void* pc = anchor->lastJavaPC();
247256
if (pc == NULL) {
257+
// Verify alignment before dereferencing sp as pointer
258+
if (!aligned(sp)) {
259+
return 0;
260+
}
248261
pc = ((const void**)sp)[-1];
249262
}
250263

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

336349
if (nm->isNMethod()) {
350+
// Check if deoptimization is in progress before walking compiled frames
351+
if (vm_thread != NULL && vm_thread->inDeopt()) {
352+
fillFrame(frames[depth++], BCI_ERROR, "break_deopt_compiled");
353+
break;
354+
}
355+
337356
int level = nm->level();
338357
FrameTypeId type = details && level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED;
339358
fillFrame(frames[depth++], type, 0, nm->method()->id());
@@ -360,7 +379,21 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
360379
// Handle situations when sp is temporarily changed in the compiled code
361380
frame.adjustSP(nm->entry(), pc, sp);
362381

363-
sp += nm->frameSize() * sizeof(void*);
382+
// Validate NMethod metadata before using frameSize()
383+
int frame_size = nm->frameSize();
384+
if (frame_size <= 0 || frame_size > MAX_FRAME_SIZE_WORDS) {
385+
fillFrame(frames[depth++], BCI_ERROR, "break_invalid_framesize");
386+
break;
387+
}
388+
389+
sp += frame_size * sizeof(void*);
390+
391+
// Verify alignment before dereferencing sp as pointer (secondary defense)
392+
if (!aligned(sp)) {
393+
fillFrame(frames[depth++], BCI_ERROR, "break_misaligned_sp");
394+
break;
395+
}
396+
364397
fp = ((uintptr_t*)sp)[-FRAME_PC_SLOT - 1];
365398
pc = ((const void**)sp)[-FRAME_PC_SLOT];
366399
continue;
@@ -407,7 +440,7 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
407440
sp = frame.senderSP();
408441
fp = *(uintptr_t*)fp;
409442
} else {
410-
pc = stripPointer(*(void**)sp);
443+
pc = stripPointer(SafeAccess::load((void**)sp));
411444
sp = frame.senderSP();
412445
}
413446
continue;
@@ -455,7 +488,21 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
455488
}
456489

457490
if (depth > 1 && nm->frameSize() > 0) {
458-
sp += nm->frameSize() * sizeof(void*);
491+
// Validate NMethod metadata before using frameSize()
492+
int frame_size = nm->frameSize();
493+
if (frame_size <= 0 || frame_size > MAX_FRAME_SIZE_WORDS) {
494+
fillFrame(frames[depth++], BCI_ERROR, "break_invalid_framesize");
495+
break;
496+
}
497+
498+
sp += frame_size * sizeof(void*);
499+
500+
// Verify alignment before dereferencing sp as pointer (secondary defense)
501+
if (!aligned(sp)) {
502+
fillFrame(frames[depth++], BCI_ERROR, "break_misaligned_sp");
503+
break;
504+
}
505+
459506
fp = ((uintptr_t*)sp)[-FRAME_PC_SLOT - 1];
460507
pc = ((const void**)sp)[-FRAME_PC_SLOT];
461508
continue;
@@ -572,7 +619,12 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
572619
}
573620

574621
if (EMPTY_FRAME_SIZE > 0 || f.pc_off != DW_LINK_REGISTER) {
575-
pc = stripPointer(*(void**)(sp + f.pc_off));
622+
// Verify alignment before dereferencing sp + offset
623+
uintptr_t pc_addr = sp + f.pc_off;
624+
if (!aligned(pc_addr)) {
625+
break;
626+
}
627+
pc = stripPointer(SafeAccess::load((void**)pc_addr));
576628
} else if (depth == 1) {
577629
pc = (const void*)frame.link();
578630
} else {

utils/run-docker-tests.sh

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# --libc=glibc|musl (default: glibc)
1111
# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9 (default: 21)
1212
# --arch=x64|aarch64 (default: auto-detect)
13-
# --config=debug|release (default: debug)
13+
# --config=debug|release|asan|tsan (default: debug)
1414
# --tests="TestPattern" (optional, specific test to run)
1515
# --gtest (enable C++ gtests, disabled by default)
1616
# --shell (drop to shell instead of running tests)
@@ -186,8 +186,8 @@ if [[ "$ARCH" != "x64" && "$ARCH" != "aarch64" ]]; then
186186
exit 1
187187
fi
188188

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

@@ -291,14 +291,14 @@ ENV DEBIAN_FRONTEND=noninteractive
291291
292292
# Install build dependencies
293293
# - libasan/libtsan for GCC sanitizers
294-
# - libclang-rt-dev for clang sanitizers and libFuzzer
294+
# - clang provides sanitizer runtimes and libFuzzer
295295
# - openssh-client for git clone over SSH
296296
RUN apt-get update && \
297297
apt-get install -y --no-install-recommends \
298298
curl wget bash make g++ clang git jq cmake \
299299
libgtest-dev libgmock-dev tar binutils libc6-dbg \
300300
ca-certificates linux-libc-dev \
301-
libasan6 libtsan0 libclang-rt-dev openssh-client && \
301+
libasan6 libtsan0 openssh-client && \
302302
rm -rf /var/lib/apt/lists/*
303303
304304
# Set up Gradle cache directory
@@ -360,7 +360,9 @@ fi
360360
# ========== Run Tests ==========
361361

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

0 commit comments

Comments
 (0)