GCOV(1)
๐ GCOV(1) ๐
GNU manual page, bestie ๐ Let's talk about gcov, the coverage tool that's lowkey the GOAT. โจ
โจ NAME โจ
gcov - coverage testing tool ๐งช
โจ SYNOPSIS โจ
gcov [-v|--version] [-h|--help]
[-a|--all-blocks]
[-b|--branch-probabilities]
[-c|--branch-counts]
[-g|--conditions]
[-d|--display-progress]
[-f|--function-summaries]
[-j|--json-format]
[-H|--human-readable]
[-k|--use-colors]
[-l|--long-file-names]
[-m|--demangled-names]
[-n|--no-output]
[-o|--object-directory directory|file]
[-p|--preserve-paths]
[-q|--use-hotness-colors]
[-r|--relative-only]
[-s|--source-prefix directory]
[-t|--stdout]
[-u|--unconditional-branches]
[-x|--hash-filenames]
files
โจ DESCRIPTION โจ
gcov is a test coverage program ๐. Use it with GCC to analyze your programs so you can write more efficient, faster code, and find the parts of your program nobody has tested yet. It also works as a profiling tool, so you can figure out where your optimization work will actually pay off. If you pair it with the other profiler, gprof, you can see which parts of your code are eating the most computing time ๐
.
Profiling tools are how you check your code's performance ๐. With a profiler like gcov or gprof, you can find out basic performance stats, like:
- how often each line of code executes
- what lines of code are actually executed
- how much computing time each section of code uses
Once you know all of that about how your code works when compiled, you can look at each module and decide which ones need optimizing. gcov helps you figure out where to put your optimization energy ๐ฏ.
Devs also use coverage testing alongside testsuites to make sure software is actually good enough for a release ๐. A testsuite checks that a program works as expected, while a coverage program checks how much of the program the testsuite actually exercises. Then devs can see what kinds of test cases to add to the testsuites for better testing and a better final product ๐.
Compile without optimization if you're planning to use gcov ๐
โโ๏ธ! Optimization can combine lines of code into one function, which may not give you enough info to find hot spots' where the code is burning a ton of computer time ๐ฅ. Also, gcov collects stats by line (at the lowest resolution), so it works best with a style that puts only one statement on each line. If you use complicated macros that expand into loops or other control structures, the stats get less helpful---they only report on the line where the macro call appears ๐. If your complex macros act like functions, swap them out for inline functions to fix that.
gcov creates a logfile called sourcefile.gcov that shows how many times each line of a source file sourcefile.c has run ๐. You can use these logfiles with gprof to fine-tune your program's performance, since gprof gives timing info you can pair with what gcov tells you.
gcov only works on code compiled with GCC ๐ซ. It is not compatible with any other profiling or test coverage mechanism, so don't try to mix it up with something else!
โจ OPTIONS โจ
Here's the full options lineup ๐ :
-a,--all-blocksโ Gives you execution counts for every single basic block ๐งฑ, not just the main blocks of each line. Normally gcov only reports counts for the main blocks of a line, so this one is how you spot blocks inside a single line that never get executed.-b,--branch-probabilitiesโ Writes branch frequencies to the output file and branch summary info to standard output ๐ฟ, so you can see how often each branch in your program was taken. Unconditional branches stay hidden unless you also pass-u.-c,--branch-countsโ Shows branch frequencies as the number of branches taken, instead of the percentage of branches taken ๐ข.-g,--conditionsโ Writes condition coverage to the output file and condition summary info to standard output ๐. This lets you see whether each condition in your program at least once had an independent effect on the outcome of the boolean expression (modified condition/decision coverage). You need to compile the source with-fcondition-coveragefor this one ๐.-d,--display-progressโ Shows the progress on standard output โณ.-f,--function-summariesโ Adds summaries for each function on top of the file-level summary ๐ง.-h,--helpโ Shows help about using gcov on standard output, then exits without doing any more processing ๐.-j,--json-formatโ Outputs the gcov file in an easy-to-parse JSON intermediate format ๐งพ that doesn't need the source code to generate. The JSON file is gzip-compressed, and the files have the.gcov.json.gzextension ๐.
๐งพ JSON structure, bestie
The JSON structure goes like this ๐ซถ:
{
"current_working_directory": "foo/bar",
"data_file": "a.out",
"format_version": "2",
"gcc_version": "11.1.1 20210510"
"files": ["$file"]
}
The root element's fields work like this:
current_working_directory: the working directory where a compilation unit was compiled ๐data_file: name of the data file (GCDA) ๐๏ธformat_version: semantic version of the format- Changes in version 2:
calls: information about function calls is added โ๏ธ
- Changes in version 2:
gcc_version: version of the GCC compiler ๐ ๏ธ
Each file has the following form ๐:
{
"file": "a.c",
"functions": ["$function"],
"lines": ["$line"]
}
The file element's fields have these meanings:
file_name: name of the source file
Each function has the following form ๐ญ:
{
"blocks": 2,
"blocks_executed": 2,
"demangled_name": "foo",
"end_column": 1,
"end_line": 4,
"execution_count": 1,
"name": "foo",
"start_column": 5,
"start_line": 1
}
The function element's fields have these meanings:
blocks: number of blocks that are in the functionblocks_executed: number of executed blocks of the functiondemangled_name: demangled name of the functionend_column: column in the source file where the function endsend_line: line in the source file where the function endsexecution_count: number of executions of the functionname: name of the functionstart_column: column in the source file where the function beginsstart_line: line in the source file where the function begins
Heads up: line numbers and column numbers both start from 1 ๐ข. In the current implementation, start_line and start_column don't include any template parameters or the leading return type, but that's likely to get fixed in the future ๐ฎ.
Each line has the following form ๐:
{
"block_ids": ["$block_id"],
"branches": ["$branch"],
"calls": ["$call"],
"count": 2,
"conditions": ["$condition"],
"line_number": 15,
"unexecuted_block": false,
"function_name": "foo",
}
Branches and calls only show up with the -b option ๐ฟ. The line element's fields have these meanings:
block_ids: IDs of basic blocks that belong to the linecount: number of executions of the lineline_number: line numberunexecuted_block: flag for whether the line contains an unexecuted block (not all statements on the line are executed) ๐ฉfunction_name: name of a function this line belongs to (for a line with inlined statements, this can be left unset)
Each branch has the following form ๐ฑ:
{
"count": 11,
"destination_block_id": 17,
"fallthrough": true,
"source_block_id": 13,
"throw": false
}
The branch element's fields have these meanings:
count: number of executions of the branchfallthrough:truewhen the branch is a fall through branchthrow:truewhen the branch is an exceptional branch โกisource_block_id: ID of the basic block where this branch happensdestination_block_id: ID of the basic block this branch jumps to
Each call has the following form โ๏ธ:
{
"destination_block_id": 1,
"returned": 11,
"source_block_id": 13
}
The call element's fields have these meanings:
returned: number of times a function call returned (call count is equal toline::count)isource_block_id: ID of the basic block where this call happensdestination_block_id: ID of the basic block this call continues after returning to
Each condition has the following form ๐งฉ:
{
"count": 4,
"covered": 2,
"not_covered_false": [],
"not_covered_true": [0, 1],
}
The condition element's fields have these meanings:
count: number of condition outcomes in this expressioncovered: number of covered condition outcomes in this expressionnot_covered_true: terms, by index, not seen as true in this expressionnot_covered_false: terms, by index, not seen as false in this expression-H,--human-readableโ Shows counts in a human-readable format, like24.6k๐.-k,--use-colorsโ Colors the lines of code that have zero coverage ๐จ. Red is for non-exceptional lines and cyan is for exceptional ones. The same colors get used for basic blocks when you pass-a.-l,--long-file-namesโ okay so this one makes long file names for included source files ๐ Say the headerx.hhas code and gets included ina.c. Running gcov ona.cgives youa.c##x.h.gcovinstead ofx.h.gcovโจ It's super useful ifx.his included in a bunch of source files and you want to see each one's individual contributions ๐ With-ptoo, both the including and included file names come out as complete path names ๐บ๏ธ-m,--demangled-namesโ displays demangled function names in the output ๐ฆ The default is to show mangled function names, so this is the flag for the readable version ๐-n,--no-outputโ don't create the gcov output file at all ๐ซ-o directory|file,--object-directory directory,--object-file fileโ this is where you point gcov at the directory with your gcov data files, or at the object path name ๐๏ธ The.gcnoand.gcdadata files get searched for using this option. If you give it a directory, the data files live in there and are named after the input file name, minus its extension. If you give it a file, the data files are named after that file, minus its extension ๐ง-p,--preserve-pathsโ keeps the complete path info in the names of generated.gcovfiles ๐ค๏ธ Without it, only the filename part gets used. With it, all directories get used:/characters turn into#,.directory components get removed, and the unremoveable..components get renamed to^. Super handy if your sourcefiles are spread across several different directories ๐ธ-q,--use-hotness-colorsโ emits perf-like colored output for hot lines ๐ฅ A legend of the color scale gets printed at the very beginning of the output file ๐-r,--relative-onlyโ only outputs info about source files with a relative pathname (after source prefix elision) ๐ Absolute paths are usually system header files, and coverage of inline functions in those is normally uninteresting.-s directory,--source-prefix directoryโ a prefix for source file names that gets removed when generating the output coverage files ๐งน It's useful when you build in a separate directory and don't want the path to the source directory messing with your output file names. Heads up: this prefix detection gets applied before deciding whether the source file is absolute โ ๏ธ-t,--stdoutโ sends the output to standard output instead of output files ๐บ-u,--unconditional-branchesโ when branch probabilities are given, this includes the ones for unconditional branches ๐ Unconditional branches are normally not interesting, so you probably won't need this one ๐-v,--versionโ displays the gcov version number on standard output, then exits without doing any further processing ๐-w,--verboseโ prints verbose info related to basic blocks and arcs ๐-x,--hash-filenamesโ when you're using--preserve-paths, gcov builds output filenames from the full pathname of the source files, and those can get so long they overflow filesystem limits ๐ญ This option makes names likesource-file##md5.gcov, wheresource-fileis the final filename part and themd5bit is calculated from the full mangled name that would've been used otherwise ๐ซถ It's the alternative to--preserve-pathsfor systems that have a filesystem limit.
gcov should be run with the current directory the same as the one you were in when you invoked the compiler, bestie ๐ Otherwise it can't find your source files. gcov produces files called mangledname.gcov in the current directory, and those contain the coverage info for the source file they match up with. You get one .gcov file for each source (or header) file that has code and was compiled to make the data files ๐ The mangledname part is usually just the source file name, but it can get more complicated if the -l or -p options are given. Check those options for the deets.
If you invoke gcov with multiple input files, the contributions from each input file get summed up โ Typically you'd invoke it with the same list of files you used for the final link of your executable ๐
The .gcov files contain the : separated fields along with your program's source code ๐
The format is:
<execution_count>:<line_number>:<source line text>
Additional block information may follow each line, if you ask for it with a command line option ๐งฉ The execution_count is - for lines with no code. Unexecuted lines get marked ##### or =====, depending on whether they're reachable by non-exceptional paths or only exceptional paths (like C++ exception handlers, respectively) ๐ With the -a option, unexecuted blocks get marked $$$$$ or %%%%%, depending on whether a basic block is reachable via non-exceptional or exceptional paths ๐ Executed basic blocks with a statement that has a zero execution_count end with a * character, and they get colored magenta with the -k option ๐ This functionality is not supported in Ada ๐
โโ๏ธ
Heads up, bestie! GCC can completely remove the bodies of functions that aren't needed, like if they're inlined everywhere. Those functions get marked with-, which can be super confusing ๐ญ Use the-fkeep-inline-functionsand-fkeep-static-functionsoptions to keep these functions and let gcov properly show their execution_count.
Some lines of info at the start have a line_number of zero. These preamble lines look like this:
-:0:<tag>:<value>
The ordering and number of these preamble lines will be augmented as gcov development progresses, so do not rely on them staying the same! Use the tag to locate a particular preamble line ๐
The additional block information looks like this:
<tag> <information>
The information is human readable, but it's designed to be simple enough for machine parsing too ๐ค
When printing percentages, 0% and 100% only get printed when the values are exactly 0% and 100% respectively ๐ฏ Other values that would normally round to 0% or 100% get printed as the nearest non-boundary value instead ๐งฎ
When using gcov, you must first compile your program with a special GCC option, --coverage ๐ This tells the compiler to generate additional info gcov needs (basically a flow graph of the program), and it also adds extra code to the object files for generating the profiling info gcov needs. These additional files get placed in the directory where the object file is located ๐
Running the program causes profile output to be generated. For each source file compiled with -fprofile-arcs, an accompanying .gcda file gets placed in the object file directory ๐
Running gcov with your program's source file names as arguments will now produce a listing of the code, along with how often each line executed ๐ For example, if your program is called tmp.cpp, this is what you see when you use the basic gcov facility:
$ g++ --coverage tmp.cpp -c
$ g++ --coverage tmp.o
$ a.out
$ gcov tmp.cpp -m
File 'tmp.cpp'
Lines executed:92.86% of 14
Creating 'tmp.cpp.gcov'
The file tmp.cpp.gcov contains the output from gcov. Here is a sample ๐
-: 0:Source:tmp.cpp
-: 0:Working directory:/home/gcc/testcase
-: 0:Graph:tmp.gcno
-: 0:Data:tmp.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:
-: 3:template<class T>
-: 4:class Foo
-: 5:{
-: 6: public:
1*: 7: Foo(): b (1000) {}
------------------
Foo<char>::Foo():
#####: 7: Foo(): b (1000) {}
------------------
Foo<int>::Foo():
1: 7: Foo(): b (1000) {}
------------------
2*: 8: void inc () { b++; }
------------------
Foo<char>::inc():
#####: 8: void inc () { b++; }
------------------
Foo<int>::inc():
2: 8: void inc () { b++; }
------------------
-: 9:
-: 10: private:
-: 11: int b;
-: 12:};
-: 13:
-: 14:template class Foo<int>;
-: 15:template class Foo<char>;
-: 16:
-: 17:int
1: 18:main (void)
-: 19:{
-: 20: int i, total;
1: 21: Foo<int> counter;
-: 22:
1: 23: counter.inc();
1: 24: counter.inc();
1: 25: total = 0;
-: 26:
11: 27: for (i = 0; i < 10; i++)
10: 28: total += i;
-: 29:
1*: 30: int v = total > 100 ? 1 : 2;
-: 31:
1: 32: if (total != 45)
#####: 33: printf ("Failure\n");
-: 34: else
1: 35: printf ("Success\n");
1: 36: return 0;
-: 37:}
Note that line 7 is shown in the report multiple times ๐ง The first occurrence shows the total number of executions of the line, and the next two belong to the instances of the Foo class constructors. As you can also see, line 30 contains some unexecuted basic blocks, so its execution count has an asterisk symbol โญ
When you use the -a option, you get individual block counts, and the output looks like this:
-: 0:Source:tmp.cpp
-: 0:Working directory:/home/gcc/testcase
-: 0:Graph:tmp.gcno
-: 0:Data:tmp.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:
-: 3:template<class T>
-: 4:class Foo
-: 5:{
-: 6: public:
1*: 7: Foo(): b (1000) {}
------------------
Foo<char>::Foo():
#####: 7: Foo(): b (1000) {}
------------------
Foo<int>::Foo():
1: 7: Foo(): b (1000) {}
------------------
2*: 8: void inc () { b++; }
------------------
Foo<char>::inc():
#####: 8: void inc () { b++; }
------------------
Foo<int>::inc():
2: 8: void inc () { b++; }
------------------
-: 9:
-: 10: private:
-: 11: int b;
-: 12:};
-: 13:
-: 14:template class Foo<int>;
-: 15:template class Foo<char>;
-: 16:
-: 17:int
1: 18:main (void)
-: 19:{
-: 20: int i, total;
1: 21: Foo<int> counter;
1: 21-block 0
-: 22:
1: 23: counter.inc();
1: 23-block 0
1: 24: counter.inc();
1: 24-block 0
1: 25: total = 0;
-: 26:
11: 27: for (i = 0; i < 10; i++)
1: 27-block 0
11: 27-block 1
10: 28: total += i;
10: 28-block 0
-: 29:
1*: 30: int v = total > 100 ? 1 : 2;
1: 30-block 0
%%%%%: 30-block 1
1: 30-block 2
-: 31:
1: 32: if (total != 45)
1: 32-block 0
#####: 33: printf ("Failure\n");
%%%%%: 33-block 0
-: 34: else
1: 35: printf ("Success\n");
1: 35-block 0
1: 36: return 0;
1: 36-block 0
-: 37:}
Okay so in this mode ๐
each basic block only gets shown on one line, and that's the last line of the block ๐ซถ. A multi-line block only adds to the execution count of that last line, and the other lines won't be shown to contain code unless some previous blocks end on those lines. The total execution count of a line is shown, and the lines after it show the counts for the individual blocks that end on that line โจ. After each block, the branch and call counts of that block get shown too, if you pass -b ๐ฅ.
Because of the way GCC instruments calls, a call count can pop up after a line with no individual blocks ๐. As you can see, line 33 has a basic block that never got executed ๐.
When you slap on the -b option, your output looks like this:
-: 0:Source:tmp.cpp
-: 0:Working directory:/home/gcc/testcase
-: 0:Graph:tmp.gcno
-: 0:Data:tmp.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:
-: 3:template<class T>
-: 4:class Foo
-: 5:{
-: 6: public:
1*: 7: Foo(): b (1000) {}
------------------
Foo<char>::Foo():
function Foo<char>::Foo() called 0 returned 0% blocks executed 0%
#####: 7: Foo(): b (1000) {}
------------------
Foo<int>::Foo():
function Foo<int>::Foo() called 1 returned 100% blocks executed 100%
1: 7: Foo(): b (1000) {}
------------------
2*: 8: void inc () { b++; }
------------------
Foo<char>::inc():
function Foo<char>::inc() called 0 returned 0% blocks executed 0%
#####: 8: void inc () { b++; }
------------------
Foo<int>::inc():
function Foo<int>::inc() called 2 returned 100% blocks executed 100%
2: 8: void inc () { b++; }
------------------
-: 9:
-: 10: private:
-: 11: int b;
-: 12:};
-: 13:
-: 14:template class Foo<int>;
-: 15:template class Foo<char>;
-: 16:
-: 17:int
function main called 1 returned 100% blocks executed 81%
1: 18:main (void)
-: 19:{
-: 20: int i, total;
1: 21: Foo<int> counter;
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0% (throw)
-: 22:
1: 23: counter.inc();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0% (throw)
1: 24: counter.inc();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0% (throw)
1: 25: total = 0;
-: 26:
11: 27: for (i = 0; i < 10; i++)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
10: 28: total += i;
-: 29:
1*: 30: int v = total > 100 ? 1 : 2;
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 31:
1: 32: if (total != 45)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 33: printf ("Failure\n");
call 0 never executed
branch 1 never executed
branch 2 never executed
-: 34: else
1: 35: printf ("Success\n");
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0% (throw)
1: 36: return 0;
-: 37:}
For each function, a line gets printed showing how many times the function is called, how many times it returns, and what percentage of the function's blocks were executed ๐.
For each basic block, a line is printed after the last line of the block, describing the branch or call that ends the basic block ๐ฆ. There can be multiple branches and calls listed for a single source line if multiple basic blocks end on that line. In that case, each branch and call gets a number ๐ญ. There's no simple way to map those back to source constructs, but in general, the lowest numbered branch or call corresponds to the leftmost construct on the source line.
For a branch, if it was executed at least once, a percentage gets printed showing the number of times the branch was taken divided by the number of times it was executed. Otherwise, the message never executed gets printed ๐.
For a call, if it was executed at least once, a percentage gets printed showing the number of times the call returned divided by the number of times it was executed ๐ธ. This will usually be 100%, but it may be less for functions that call exit or longjmp, since they may not return every time they're called.
The execution counts are cumulative ๐. If the example program ran again without removing the .gcda file, the count for each line in the source would be added to the results of the previous run(s). This is potentially useful in a few ways. For example, it could accumulate data over a bunch of program runs as part of a test verification suite, or give you more accurate long-term info across a large number of program runs ๐ง.
The data in the .gcda files is saved immediately before the program exits. For each source file compiled with -fprofile-arcs, the profiling code first attempts to read in an existing .gcda file. If the file doesn't match the executable (differing number of basic block counts), it ignores the contents of the file. Then it adds in the new execution counts and finally writes the data to the file ๐.
โจ Using gcov with GCC Optimization โจ
If you plan to use gcov to help optimize your code, you must first compile your program with a special GCC option, --coverage ๐. Aside from that, you can use any other GCC options, but if you want to prove that every single line in your program was executed, you should not compile with optimization at the same time ๐
. On some machines the optimizer can eliminate some simple code lines by combining them with other lines. For example, code like this:
if (a != b)
c = 1;
else
c = 0;
can be compiled into one instruction on some machines ๐คฏ. In this case, there's no way for gcov to calculate separate execution counts for each line, because there isn't separate code for each line. So the gcov output looks like this if you compiled the program with optimization:
100: 12:if (a != b)
100: 13: c = 1;
100: 14:else
100: 15: c = 0;
The output shows that this block of code, combined by optimization, executed 100 times. In one sense that result is correct, because there was only one instruction representing all four of these lines ๐ค. However, the output doesn't indicate how many times the result was 0 and how many times it was 1 ๐คท.
Inlineable functions can create unexpected line counts ๐. Line counts are shown for the source code of the inlineable function, but what gets shown depends on where the function is inlined, or whether it isn't inlined at all ๐ซ .
Okay so if the function isn't inlined, the compiler has to make an out of line copy of it in every object file that needs it ๐ง. If fileA.o and fileB.o both have out of line bodies for the same inlineable function, they'll both carry coverage counts for it too ๐
. When you link them, the linker on lots of systems picks ONE of those bodies for every call and ignores or removes the other. Here's the tea though: it does NOT clean up the coverage counters for the unused copy ๐. So when you instrument stuff, every use of that function except one shows zero counts ๐ญ.
If the function gets inlined in several places, the block structure in each spot might not match up. A condition could be calculable at compile time in some instances but not others ๐. Since the coverage for all the uses gets shown against the same source lines, the line counts might look kinda inconsistent. Don't panic, it's just how it works ๐ธ.
Long-running apps can use the __gcov_reset and __gcov_dump facilities to limit profile collection to the part of the program you actually care about โจ. Calling __gcov_reset(void) clears all run-time profile counters back to zero ๐งน, and calling __gcov_dump(void) dumps the profile info collected up to that point into .gcda output files ๐. Instrumented apps use a static destructor with priority 99 to invoke __gcov_dump, so __gcov_dump runs after all user defined static destructors AND handlers registered with atexit ๐. Main character timing, honestly ๐ฆ.
If an executable loads a dynamic shared object via dlopen functionality, you need -Wl,--dynamic-list-data to dump all profile data. Don't skip this one, bestie ๐จ
The profiling run-time library reports various errors related to profile manipulation and profile saving ๐. These get printed to standard error output, or to the GCOV_ERROR_FILE file if you're using that environment variable ๐. If you want it to terminate immediately after an error occurs, set the GCOV_EXIT_AT_ERROR environment variable ๐. This can help you catch profile clashing, which is what leads to a misleading profile ๐ต๏ธโโ๏ธ.
โจ SEE ALSO โจ
gpl(7), gfdl(7), fsf-funding(7), gcc(1) and the Info entry for gcc ๐๐
โจ COPYRIGHT โจ
Copyright (c) 1996-2024 Free Software Foundation, Inc. ๐
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation ๐. The Invariant Sections are "GNU General Public License" and "Funding Free Software" ๐ซถ. The Front-Cover texts are (a) (see below), and the Back-Cover Texts are (b) (see below) ๐. A copy of the license is included in the gfdl(7) man page ๐.
(a) The FSF's Front-Cover Text is:
A GNU Manual
(b) The FSF's Back-Cover Text is:
You have freedom to copy and modify this GNU Manual, like GNU software. Copies published by the Free Software Foundation raise funds for GNU development.
Bestie, that's the whole glow-up ๐โจ This page is from gcc-14, last touched on 2024-04-12, and it's officially GCOV(1) era ๐ ๐๐ฆ
๐ฆ where this page comes from
- ubuntu noble: gcc-14-powerpc64-linux-gnu 14-20240412-0ubuntu1cross1
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms