Look at asm output from gcc -g -S
and you'll see .line
debug-info directives and so on for the block of asm corresponding to the C source line.
(With optimization enabled the same line can map to multiple non-contiguous instructions, so it gets much trickier, but compilers still try to be useful and map most instructions to some source line even if they're really the result of optimization and doing an operation that doesn't appear in the source...).
https://godbolt.org/ uses the same debug info as debuggers do, but uses it for color highlighting to match source lines with asm.
When an assembler assembles these .line
directives, it creates debug info in the .o
object file, which is eventually linked into an executable or library. Or split into a separate debug-symbols file. Or stripped.
It's this debug-info that debuggers read.
(Debug info also includes info about which named C variables are stored where, and what their types are. For locals, the locations are relative to the stack frame for the function that contains them.)