-g compiler option, avr-gcc only generates line number and other debug information for C (and C++) files that pass the compiler. Functions that don't have line number information will be completely skipped by a single step command in gdb. This includes functions linked from a standard library, but by default also functions defined in an assembler source file, since the -g compiler switch does not apply to the assembler.
So in order to debug an assembler input file (possibly one that has to be passed through the C preprocessor), it's the assembler that needs to be told to include line-number information into the output file. (Other debug information like data types and variable allocation cannot be generated, since unlike a compiler, the assembler basically doesn't know about this.) This is done using the (GNU) assembler option --gstabs.
Example:
$ avr-as -mmcu=atmega128 --gstabs -o foo.o foo.s
When the assembler is not called directly but through the C compiler frontend (either implicitly by passing a source file ending in .S, or explicitly using -x assembler-with-cpp), the compiler frontend needs to be told to pass the --gstabs option down to the assembler. This is done using -Wa,--gstabs. Please take care to only pass this option when compiling an assembler input file. Otherwise, the assembler code that results from the C compilation stage will also get line number information, which confuses the debugger.
-Wa,-gstabs since the compiler will add the extra '-' for you.
$ EXTRA_OPTS="-Wall -mmcu=atmega128 -x assembler-with-cpp"
$ avr-gcc -Wa,--gstabs ${EXTRA_OPTS} -c -o foo.o foo.S
Also note that the debugger might get confused when entering a piece of code that has a non-local label before, since it then takes this label as the name of a new function that appears to have been entered. Thus, the best practice to avoid this confusion is to only use non-local labels when declaring a new function, and restrict anything else to local labels. Local labels consist just of a number only. References to these labels consist of the number, followed by the letter b for a backward reference, or f for a forward reference. These local labels may be re-used within the source file, references will pick the closest label with the same number and given direction.
Example:
myfunc: push r16
push r17
push r18
push YL
push YH
...
eor r16, r16 ; start loop
ldi YL, lo8(sometable)
ldi YH, hi8(sometable)
rjmp 2f ; jump to loop test at end
1: ld r17, Y+ ; loop continues here
...
breq 1f ; return from myfunc prematurely
...
inc r16
2: cmp r16, r18
brlo 1b ; jump back to top of loop
1: pop YH
pop YL
pop r18
pop r17
pop r16
ret