Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gcov] source file location error due to miss current_working_directory in gcno file #121368

Open
PikachuHyA opened this issue Dec 31, 2024 · 5 comments · May be fixed by #121369 or #121420
Open

[gcov] source file location error due to miss current_working_directory in gcno file #121368

PikachuHyA opened this issue Dec 31, 2024 · 5 comments · May be fixed by #121369 or #121420
Labels

Comments

@PikachuHyA
Copy link
Contributor

Background

I am migrating an internal codebase from GCC to Clang and have encountered a discrepancy in code coverage reporting between the two compilers. Specifically, Clang fails to correctly locate source files when generating coverage data, whereas GCC operates as expected.

Reproducer

// main.cc
#include <iostream>
int main() {
    std::cout << "Hello World\n" << std::endl;
    return 0;
}

Building with GCC (Works Correctly)
GCC Version: 10.2.1

g++ main.cc --coverage -c -o main.o 
g++ main.o --coverage -o main.bin.gcc
./main.bin.gcc
lcov --directory . --capture --output-file app.info
genhtml app.info -o html

Building with Clang (Fails)

Clang Options: -Xclang -coverage-version=B02A to ensure compatibility of .gcno and .gcda files

if gcc is 9.2.1, use -Xclang -coverage-version=A92A

clang++ main.cc -c -o main.o --coverage -Xclang -coverage-version=B02A
clang++ main.o --coverage -o main.bin.clang
./main.bin.clang
lcov --directory . --capture --output-file app.info
genhtml app.info -o html

Error Output

+ lcov --directory . --capture --output-file app.info
Capturing coverage data from .
Found gcov version: 10.2.1
Using intermediate gcov format
Scanning . for .gcda files ...
Found 1 data files in .
Processing main.gcda
geninfo: WARNING: could not open /main.cc
geninfo: WARNING: some exclusion markers may be ignored
Finished .info-file creation
+ genhtml app.info -o html
Reading data file app.info
Found 1 entries.
No common filename prefix found!
Writing .css and .png files.
Generating output.
Processing file /main.cc
genhtml: ERROR: cannot read /main.cc

I also provide a demo repository that reproduces the issue using GitHub Actions (Ubuntu 22.04, GCC 11.4.0, lcov 1.14). You can view the workflow run here.

Root Cause

The issue arises because the current_working_directory field in the .gcno file is empty when using Clang. GCC correctly sets this field, allowing gcov and related tools to resolve the relative paths of source files. Clang's omission leads to the inability of lcov and genhtml to locate the source files, resulting in coverage data processing errors.

Relevant Code Snippet from LLVM:

writeString(""); // unuseful current_working_directory

Fix

diff --git a/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp b/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
index 2ea89be40a3d..eb6771a2b6ee 100644
--- a/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
+++ b/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
@@ -972,8 +972,13 @@ bool GCOVProfiler::emitProfileNotes(
         out.write(Tmp, 4);
       }
       write(Stamp);
-      if (Version >= 90)
-        writeString(""); // unuseful current_working_directory
+      if (Version >= 90) {
+        SmallString<256> CWD;
+        llvm::sys::fs::current_path(CWD);
+        // the current_working_directory is used by gcov
+        // if the source file is relative path
+        writeString(CWD);
+      }
       if (Version >= 80)
         write(0); // unuseful has_unexecuted_blocks
 

tested with GCC 9.2.1 and GCC 10.2.1, lcov 1.0.0

Reference

@PikachuHyA
Copy link
Contributor Author

cc @MaskRay for visibility

please feel free to share your thoughts

@MaskRay
Copy link
Member

MaskRay commented Dec 31, 2024

llvm omitting the working directory helps with local determinism (https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html), but I agree that the directory is useful in some scenarios. In GCC, -ffile-prefix-map doesn't change the working directory in .gcno files, perhaps it should be changed.

If we add the CWD to .gcno files, llvm/lib/ProfileData/GCOV.cpp for llvm-cov gcov should be changed as well. lcov by default probes gcov from GCC.

@MaskRay
Copy link
Member

MaskRay commented Jan 1, 2025

It seems non-trivial to remap the path. writeString("."); seems sufficient to make gcov happy.

@PikachuHyA
Copy link
Contributor Author

@MaskRay Thank you for your feedback.

I agree that adding the CWD would break the local determinism.

Implementing writeString("."); allows the producer to function correctly. However, after testing it with my codebase, the issue persists.

Do you have any suggestions on how to handle changes in the CWD? Specifically, how can we manage situations where the source files are in one directory and lcov is invoked from another? For example, consider having main.c in the src directory and running lcov from the codecoverage directory.

Error Output:

$ make -C codecoverage report
lcov --directory .. --capture --output-file app.info
Capturing coverage data from ..
Found gcov version: 10.2.1
Using intermediate gcov format
Scanning .. for .gcda files ...
Found 1 data files in ..
Processing main.gcda
geninfo: WARNING: could not open ./main.c
geninfo: WARNING: some exclusion markers may be ignored
Finished .info-file creation
genhtml app.info -o html
Reading data file app.info
Resolved relative source file path "./main.c" with CWD to "/tmp/cpp_cov_examples/makefile_example/codecoverage/main.c".
Found 1 entries.
Found common filename prefix "/tmp/cpp_cov_examples/makefile_example"
Writing .css and .png files.
Generating output.
Processing file codecoverage/main.c
genhtml: ERROR: cannot read /tmp/cpp_cov_examples/makefile_example/codecoverage/main.c
make: *** [Makefile:5: report] Error 2

I have also added the test case in this commit.

Here is the code

Directory Structure:

$ tree
.
├── codecoverage
│   └── Makefile
├── Makefile
└── src
    ├── main.c
    └── Makefile

2 directories, 4 files

Top-Level Makefile:

$ cat Makefile
all:
	$(MAKE) -C src all

clean:
	$(MAKE) -C src $@
	$(MAKE) -C codecoverage $@

codecoverage/Makefile:

$ cat codecoverage/Makefile
all:

report: /usr/bin/lcov
	lcov --directory .. --capture --output-file app.info
	genhtml app.info -o html

clean:
	rm -rf html *.info

.PHONY: report

src/Makefile:

$ cat src/Makefile
all: main

CFLAGS = --coverage

ifeq ($(shell $(CC) --version | grep "clang"),)
    CC = gcc
else
    CC = clang
	GCC_VERSION := $(shell gcc --version | grep -oP '\d+\.\d+\.\d+' | head -1)
	GCOV_VERSION_OPTION := ""
ifeq ($(GCC_VERSION), 10.2.1)
    GCOV_VERSION_OPTION := -Xclang -coverage-version=B02A
else
    $(echo Unsupported GCC version: $(GCC_VERSION))
endif
    CFLAGS += $(GCOV_VERSION_OPTION)
endif

%: %.o
	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

main.o: main.c

main: main.o
	$(CC) $(CFLAGS) $^ -o main.bin.$(CC)

clean:
	rm -rf *.o main.bin.* *.gcno *.gcda

src/main.c:

// main.c
#include <stdio.h>

int main() {
  printf("Hello World\n");
  return 0;
}

@MaskRay
Copy link
Member

MaskRay commented Jan 3, 2025

GCC 12 has significantly changed its gcov format.

I have implemented GCC compatible instrumentation up to 11 (e.g. clang --coverage -Xclang -coverage-version=B11* for 11.1)
and llvm-cov gcov support up to 12 (without --json-format support)

clang --coverage -Xclang -coverage-version=B21* for 12.1 is not compatible gcov from GCC 12 and this writeString(".") thing is not the only issue.

However, you can use Clang with llvm-cov gcov instead.
https://maskray.me/blog/2020-09-27-gcov-and-llvm#lcov has an example.

lcov by default probes gcov. You can create a shell script llvm-gcov

#!/bin/sh
exec llvm-cov gcov "$@"

then use

clang --coverage a.c -o a  # generate a.gcno
./a  # generate a.gcda
lcov -c --gcov-tool $PWD/llvm-gcov -d . -o output.lcov
genhtml output.lcov -o out

You could also migrate to https://clang.llvm.org/docs/SourceBasedCodeCoverage.html . It is based on Clang AST instead of debug info and more precise region information.
It's not supported by lcov, though.


The "current_working_directory" field in .gcno seems to be read by lcov: linux-test-project/lcov@75fbae1

If your source files are all in the same directory, perhaps you could invoke chdir then lcov....
clang ... $PWD/a.cc is perhaps another workaround. While we could change Clang to embed getpwd to .gcno files, the changes seem quite invasive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants