33# SPDX-License-Identifier: Apache-2.0
44
55import contextlib
6+ import io
67import os
78import re
8- import shutil
9- import subprocess
109import warnings
1110
1211import pytest
12+ from elftools .elf .elffile import ELFFile
1313
1414from cuda .core import _linker
1515from cuda .core ._device import Device
@@ -370,9 +370,36 @@ def test_program_options_name_accepts_none(name):
370370 assert options ._name == expected .encode ()
371371
372372
373+ def _dwarf_source_path (cubin : bytes ) -> str :
374+ """The source path a debugger resolves for ``cubin``.
375+
376+ A cubin is an ELF64 image carrying ordinary DWARF, so the compilation
377+ unit's ``DW_AT_name`` is the path cuda-gdb opens to display source.
378+ """
379+ with io .BytesIO (cubin ) as stream :
380+ dwarf = ELFFile (stream ).get_dwarf_info (relocate_dwarf_sections = False )
381+ units = list (dwarf .iter_CUs ())
382+ assert len (units ) == 1 , f"expected one compilation unit, got { len (units )} "
383+
384+ top = units [0 ].get_top_DIE ()
385+ path = top .attributes ["DW_AT_name" ].value .decode ()
386+ comp_dir = top .attributes .get ("DW_AT_comp_dir" )
387+ if not os .path .isabs (path ) and comp_dir is not None :
388+ path = os .path .join (comp_dir .value .decode (), path )
389+
390+ # The line table is what actually maps addresses to lines, so confirm it
391+ # names the same file instead of trusting DW_AT_name alone. Comparing
392+ # basenames avoids the dir_index encoding, which differs across DWARF
393+ # versions.
394+ listed = {entry .name .decode () for entry in dwarf .line_program_for_CU (units [0 ]).header ["file_entry" ]}
395+ assert os .path .basename (path ) in listed , f"{ path !r} absent from line table { sorted (listed )} "
396+
397+ return path
398+
399+
373400@pytest .mark .human_reviewed
374401@pytest .mark .parametrize ("name" , [None , "my_program" ])
375- def test_program_string_source_debug (name , tmp_path ):
402+ def test_program_string_source_debug (name ):
376403 # when a file doesn't exist on disk, nvrtc
377404 # still ends up referencing a file it thinks is there
378405 # in the dwarf table. As a WAR, we put the file there
@@ -382,33 +409,9 @@ def test_program_string_source_debug(name, tmp_path):
382409 program = Program (code , "c++" , options = {"name" : name , "debug" : True , "lineinfo" : True })
383410 cubin = program .compile ("cubin" )
384411
385- # read dwarf table
386- nvdisasm = shutil .which ("nvdisasm" )
387-
388- cubin_path = tmp_path / "program.cubin"
389- cubin_path .write_bytes (bytes (cubin .code ))
390-
391- result = subprocess .run ( # noqa: S603
392- [nvdisasm , "-g" , str (cubin_path )],
393- capture_output = True ,
394- text = True ,
395- errors = "replace" ,
396- )
397- if result .returncode != 0 :
398- pytest .fail (f"nvdisasm -g failed with exit code { result .returncode } \n { result .stderr } " , pytrace = False )
399-
400- # -g annotates each instruction with the line-table entry that covers it:
401- # //## File "/abs/dir/name.cu", line 12
402- # The directory comes from the line table's directory table, so this is the
403- # fully resolved path cuda-gdb will try to open.
404- paths = set (re .findall (r'//## File "([^"]+)", line \d+' , result .stdout ))
405- assert len (paths ) == 1 , f"expected exactly one source file in the line table, got { sorted (paths )} "
406- dwarf_path = paths .pop ()
407-
408- # cuda-gdb opens this path literally, so the source has to actually be
409- # sitting there for source-level debugging to work.
410- assert os .path .isfile (dwarf_path )
412+ dwarf_path = _dwarf_source_path (bytes (cubin .code ))
411413
414+ assert os .path .isfile (dwarf_path ), f"no source file on disk at the DWARF path { dwarf_path !r} "
412415 with open (dwarf_path , encoding = "utf-8" ) as source_file :
413416 assert source_file .read ().splitlines () == code .splitlines ()
414417
0 commit comments