unit test "functionalities/step-avoids-no debug"

the test case “test_step_over_with_dwarf_python” for unit test "functionalities/step-avoids-no debug” passes when compiling the unit test with clang and fails when compiling with gcc, when i run the unit test in linux (ubuntu 14.04). note: to run this test case in linux, you have to comment out the “@skipIfLinux # intermittent failure” line.

the failure happens when the python code in TestStepNoDebug.py does the first self.thread.StepOut() after the breakpoint.

this appears to be caused by gcc generating significantly different code than clang. clang associates 20 bytes of machine code (all of the machine code related to the source line) to the breakpoint source line (line 12), but gcc only associates the first 3 bytes of machine code to line 12. gcc associates the remaining bytes related to line 12 with line 13. the corresponding code for both versions is shown at the bottom of this email.

this difference means that when the self.thread.StepOut() is executed in the clang version, the program steps to the source line of the calling routine (line 19), but the gcc version steps to the next source line (line 13). this mismatch causes TestStepNoDebug.py to report a failure not really caused by lldb, but really just a compiler difference.

it seems like the tests in TestStepNoDebug.py should be rewritten to include different tests for clang and gcc, or the test should be restructured to not rely on compiler differences - at least not clang and gcc differences.

shown below is the objdump for the clang version. the 20 bytes from 40055c thru 40056f (inclusive) are associated with source line 12 (no bytes are associated with source line 13).

int
called_from_nodebug_actual(int some_value)
{
400530: 55 push %rbp
400531: 48 89 e5 mov %rsp,%rbp
400534: 48 83 ec 10 sub $0x10,%rsp
400538: 48 b8 d4 06 40 00 00 movabs $0x4006d4,%rax
40053f: 00 00 00
400542: 89 7d fc mov %edi,-0x4(%rbp)
int return_value = 0;
400545: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
return_value = printf (“Length: %d.\n”, some_value);
40054c: 8b 75 fc mov -0x4(%rbp),%esi
40054f: 48 89 c7 mov %rax,%rdi
400552: b0 00 mov $0x0,%al
400554: e8 b7 fe ff ff callq 400410 printf@plt
400559: 89 45 f8 mov %eax,-0x8(%rbp)
return return_value; // Stop here and step out of me
40055c: 8b 45 f8 mov -0x8(%rbp),%eax
40055f: 48 83 c4 10 add $0x10,%rsp
400563: 5d pop %rbp
400564: c3 retq
400565: 66 66 2e 0f 1f 84 00 data32 nopw %cs:0x0(%rax,%rax,1)
40056c: 00 00 00 00

0000000000400570 <called_from_nodebug>:
}

shown below is the objdump for the gcc version. the 3 bytes from 400556 thru 400558 (inclusive) are associated with source line 12, and the 2 bytes from 400559 thru 40055a (inclusive) are associated with source line 13.

called_from_nodebug_actual(int some_value)
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 20 sub $0x20,%rsp
400535: 89 7d ec mov %edi,-0x14(%rbp)
int return_value = 0;
400538: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
return_value = printf (“Length: %d.\n”, some_value);
40053f: 8b 45 ec mov -0x14(%rbp),%eax
400542: 89 c6 mov %eax,%esi
400544: bf 94 06 40 00 mov $0x400694,%edi
400549: b8 00 00 00 00 mov $0x0,%eax
40054e: e8 bd fe ff ff callq 400410 printf@plt
400553: 89 45 fc mov %eax,-0x4(%rbp)
return return_value; // Stop here and step out of me
400556: 8b 45 fc mov -0x4(%rbp),%eax
}
400559: c9 leaveq
40055a: c3 retq

000000000040055b <called_from_nodebug>:

doug

Hey I wonder if we can solve this using your thought on making it compiler agnostic, by having lldb use a “step out” rather than a “step” at that point? I would hope the debugger can manage to step out of the function doing that approach regardless of the “number of steps” it would take to do it with a gcc vs. clang exe.

Just a thought. That would avoid needing separate paths for gcc and clang.

Otherwise, it for sure seems like the current test flow would need to change based on compiler. I like going with the first approach, if that’s valid, so we can avoid having (say) yet another compiler do something else which requires yet a different path.

-Todd

that’s worth a try.

i’ll work on that this morning

Great!

IME line tables coming from most compilers are flakey and odd, and the debugger just has to suck it up and deal with them as best it can. Sometimes we see cases where the debug info is really wrong (like having line numbers associated with code that clearly doesn't belong to them, or weird incorrect block structure, etc...) in one compiler or the other. If we're facing such a case, then we should not make separate tests for the two compilers, since then we are testing incorrect behavior, but should xfail the test for that compiler and maybe file a bug about the bad debug info. If there's little chance that the compiler will actually be fixed, then we should also put a bug on lldb to work around whatever quirk the line tables are showing (it already has a bunch of these workarounds to deal with odd clang line tables...) It may or may not be able to do that safely. If the latter is true, then the test really should fail for that compiler.

On the other hand there are cases where the code+line tables are confusing but arguably correct in one compiler or the other. In that case, lldb should be able to step reasonably in both cases, and the fact that it doesn't is a bug in lldb. In that case, we should xfail the test and file a bug with lldb.

Another alternative I've had to use when the point of the test is getting somewhere past the problem area and the line tables have been odd - particularly when they are odd in varying ways over time - is to write the test case more carefully: step, look where I landed, then if needed step again. That seems a better way to go than writing separate test cases as well.

I don't think it is a good idea to write separate tests for the two compilers, however. lldb should be able to handle anything that is arguably correct but weird, or weird but predictably weird. Anything beyond that seems either to be covering a bug in the compilers or in lldb or both.

Jim

in routine “do_step_over_past_debug()” in TestSteoNoDebug.py, if i change the first and third “self.thread.StepOver()” to “self.thread.StepOut()”, test “test_step_over_with_dwarf” will pass with gcc (and not break with clang).

however, i’m not sure if this change is bypassing the intention of test_step_over_with_dwarf. this change may make gcc pass, but is probably hiding suspect gcc behaviour and may break the original intention of test_step_over_with_dwarf.

doug