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
130 changes: 130 additions & 0 deletions gdb/testsuite/gdb.rocm/runtime-core-attached.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* Copyright (C) 2023-2026 Free Software Foundation, Inc.
Copyright (C) 2023-2026 Advanced Micro Devices, Inc. All rights reserved.

This file is part of GDB.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

#include <hip/hip_runtime.h>
#include <thread>
#include <chrono>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "gdb_watchdog.h"

#define CHECK(cmd) \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the version from rocm-test-utils.h.

do \
{ \
hipError_t error = cmd; \
if (error != hipSuccess) \
{ \
fprintf (stderr, "error: '%s'(%d) at %s:%d\n", \
hipGetErrorString (error), error, __FILE__, __LINE__); \
exit (EXIT_FAILURE); \
} \
} while (0)

/* Pagefault kernel. In this testcase, OUT contains an address not reachable
by the GPU, triggering a page fault. */

__global__ void
pagefault_kernel (int *out)
{
int local = 42;
*out = 8;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int local = 42 is unused by the test — unlike runtime-core.exp, this test never runs p local. Either remove it, or add a comment explaining it is intentionally left for future frame-variable checks (as runtime-core.cpp uses it). Same applies to abort_kernel (line 61) and aux_kernel (line 73).

}

/* This kernel will call abort (s_trap 2), which should cause the runtime to
generate a core dump. */

__global__ void
abort_kernel ()
{
int local = 42;
abort ();
}

/* Secondary kernel, meant to run concurrently on a separate stream. This
kernel is meant to be running when the "main" kernel will generate an
exception. This is to ensure that GDB can load kernels which have raised an
exception (and entered the trap handler) and kernels which have not. */

__global__ void
aux_kernel ()
{
int local = 72;

while (true)
__builtin_amdgcn_s_sleep (1);
}

enum testcase_t
{
memfault,
abort
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abort as an enum member shadows the global ::abort() called by abort_kernel a few lines above. This compiles in C++ because the enum member is only in scope where the type is used as testcase_t::abort, but it is confusing and fragile. Rename it, e.g. kabort or do_abort, as is idiomatic in GDB test sources — see runtime-core.cpp which has the same pattern and the same risk.


int
main (int argc, char **argv)
{
/* Make sure that the process terminates if the exception is not caught by
the ROCr runtime. */
gdb_watchdog (30);

if (argc != 2)
{
std::cerr
<< "Usage: " << argv[0] << " pagefault|abort" << std::endl;
return EXIT_FAILURE;
}

std::string teststr = argv[1];
testcase_t test;
if (teststr == "pagefault")
test = testcase_t::memfault;
else if (teststr == "abort")
test = testcase_t::abort;
else
{
std::cerr << "Invalid test name \"" << teststr << "\"" << std::endl;
return EXIT_FAILURE;
}

hipStream_t st1;
hipStream_t st2;

CHECK (hipStreamCreate (&st1));
CHECK (hipStreamCreate (&st2));

aux_kernel<<<1, 1, 0, st1>>> ();

/* Make sure that the aux kernel gets time to start. */
std::this_thread::sleep_for (std::chrono::seconds { 2 });

switch (test)
{
case testcase_t::memfault:
{
int *out = nullptr;
pagefault_kernel<<<1, 1, 0, st2>>> (out);
break;
}
case testcase_t::abort:
abort_kernel<<<1, 1, 0, st2>>> ();
break;
};

CHECK (hipDeviceSynchronize ());
}
118 changes: 118 additions & 0 deletions gdb/testsuite/gdb.rocm/runtime-core-attached.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright (C) 2026 Free Software Foundation, Inc.
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.

# This file is part of GDB.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Test core dump generation while GDB is attached.
# Verify that running a program under GDB with signal passthrough
# generates both host and GPU core dumps.

load_lib rocm.exp

require allow_rocm_core_tests
require allow_hip_tests

standard_testfile .cpp

if { [build_executable "failed to prepare" ${testfile} ${srcfile} \
{debug hip}] } {
return -1
}

proc do_attached_test { fault } {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attached_coredump_test?


set coredir [standard_output_file "coredir-$fault"]
remote_exec build "mkdir -p $coredir"
remote_exec build "rm -f $coredir/core*"

clean_restart $::testfile
gdb_test "handle SIGABRT nostop pass" ".*"
gdb_test "handle SIGSEGV nostop pass" ".*"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

".*" as the expected pattern records no meaningful output check and produces a generic pass/fail. Use gdb_test_no_output if no output check is intended, or supply a real pattern such as "Signal.*SIGABRT.*No.*Yes.*" so the signal-handler table is at least spot-checked. Same for the SIGSEGV handle command on the next line.

gdb_test_no_output "set cwd $coredir"
gdb_test_no_output "set args $fault"

gdb_run_cmd

# Wait for program to crash and cores to be written. This can take
# a while for large core dumps.
with_timeout_factor 3 {
gdb_test_multiple "" "wait for program exit" {
-re "Program terminated with signal" {
exp_continue
}
-re "The program no longer exists" {
exp_continue
}
-re "$::gdb_prompt $" {
pass $gdb_test_name
}
timeout {
fail "$gdb_test_name (timeout)"
return
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gdb_test_multiple seems much.

The "timeout" should be handled by default by gdb_test_multiple (or gdb_test), and the other cases only use exp_continue. All this matches is that we got the prompt back, which we could as well do with gdb_test "run" "The program no longer exists", couldn't we?.


set allcore_list [glob -nocomplain -directory $coredir "core*"]
set gpucore_list {}
Comment thread
lancesix marked this conversation as resolved.
set hostcore_list {}

foreach core $allcore_list {
if {[string match "*.gpu" $core]} {
lappend gpucore_list $core
} else {
lappend hostcore_list $core
}
}

if {[llength $gpucore_list] != 1} {
unsupported "single GPU core file must exist (found [llength $gpucore_list])"
return
}

if {[llength $hostcore_list] != 1} {
unsupported "single host core file must exist (found [llength $hostcore_list])"
return
}

set gpucore [lindex $gpucore_list 0]
set hostcore [lindex $hostcore_list 0]

pass "generated both cores while gdb attached"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pass fires before coremerge is invoked (line 100) and before gdb_assert checks that the merged core exists (line 101). A coremerge failure will still leave this pass on record. Move the pass to after the gdb_assert, or fold it into the assert message.

set coremerge [gdb_find_coremerge]
set merged_core "$coredir/merged-$fault.core"
remote_exec build "$coremerge $merged_core $hostcore $gpucore"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of remote_exec build "$coremerge ..." is ignored. If coremerge is not found or exits non-zero, the subsequent gdb_assert fires with a confusing message. Follow the pattern in rocm.exp:599-604: check [remote_file build exists $merged_core] immediately and emit a warning with the coremerge stderr if it is absent, then return early.

gdb_assert {[remote_file build exists $merged_core]} "merged cores"

clean_restart $::testfile
gdb_test "target core $merged_core" \
[multi_line \
".*" \
"Core was generated by .*" \
"Program terminated with signal SIG(ABRT|SEGV).*" \
"Thread $::decimal \\(AMDGPU Wave \[^\r\n\]*\\).*"] \
"load $fault merged corefile"

gdb_test "info threads" "AMDGPU Wave.*" "verify GPU threads"
}

with_rocm_gpu_lock {
foreach_with_prefix fault {pagefault abort} {
do_attached_test $fault
}
}
Loading