--- Comment #11 from Kevin Buettner <kevinb at redhat dot com> ---
I've simplified Jakub's example slightly:

--- vau2.c ---
struct A { int a; };
struct B { struct A *b; };
struct C { struct B *c; };

__attribute__((noipa)) bool
foo (struct A *p)
  return false;

__attribute__((noipa)) int
baz (int x)
  return 0;

__attribute__((noipa)) void
qux (struct C *p)
  struct A *a;
  bool b;
  int c;

  if (!p->c) __builtin_abort ();
  a = p->c->b;

  b = (a->a == 4)
        && (foo (a));

  c = baz (0);
  baz (b);

main ()
  struct A a = { 4 };
  struct B b = { &a };
  struct C c = { &b };
  qux (&c);
  return 0;
--- end vau2.c ---

When I compile this via "g++ -O2 -g vau2.c -o vau2", and load it into gdb, it
exhibits the same behavior shown by Jakub.  I.e. the following sequence...

b qux

... does not stop in foo as expected.  The program instead exits.

It turns out that qux consists of two disjoint pieces which, for some reason,
are separated by a lot of other code, e.g. main, _start, foo, baz, and a lot of
other stuff too.  Here's what it looks like:

   0x400460 <qux(C*)>:  callq  0x400430 <abort@plt>
   0x400465:    nopw   %cs:0x0(%rax,%rax,1)
   0x40046f:    nop
   0x400470 <main()>:   sub    $0x28,%rsp
   0x400474 <main()+4>: lea    0xc(%rsp),%rax
   0x400479 <main()+9>: lea    0x18(%rsp),%rdi
   0x40047e <main()+14>:        movl   $0x4,0xc(%rsp)
   0x400486 <main()+22>:        mov    %rax,0x10(%rsp)
   0x40048b <main()+27>:        lea    0x10(%rsp),%rax
   0x400490 <main()+32>:        mov    %rax,0x18(%rsp)
   0x400495 <main()+37>:        callq  0x4005b0 <_Z3quxP1C>
   0x40049a <main()+42>:        xor    %eax,%eax
   0x40049c <main()+44>:        add    $0x28,%rsp
   0x4004a0 <main()+48>:        retq   
   0x4004a1:    nopw   %cs:0x0(%rax,%rax,1)
   0x4004ab:    nopl   0x0(%rax,%rax,1)
   0x4004b0 <_start>:   xor    %ebp,%ebp
   0x4004b2 <_start+2>: mov    %rdx,%r9
   0x4004b5 <_start+5>: pop    %rsi
   0x4004b6 <_start+6>: mov    %rsp,%rdx
   0x4004b9 <_start+9>: and    $0xfffffffffffffff0,%rsp
   0x4004bd <_start+13>:        push   %rax
   0x4004be <_start+14>:        push   %rsp
   0x4004bf <_start+15>:        mov    $0x400660,%r8
   0x4004c6 <_start+22>:        mov    $0x4005f0,%rcx
   0x4004cd <_start+29>:        mov    $0x400470,%rdi
   0x4004d4 <_start+36>:        callq  0x400440 <__libc_start_main@plt>
   0x4004d9 <_start+41>:        hlt    
   0x400590 <foo(A*)>:  xor    %eax,%eax
   0x400592 <foo(A*)+2>:        retq   
   0x400593:    nopl   (%rax)
   0x400596:    nopw   %cs:0x0(%rax,%rax,1)
   0x4005a0 <baz(int)>: xor    %eax,%eax
   0x4005a2 <baz(int)+2>:       retq   
   0x4005a3:    nopl   (%rax)
   0x4005a6:    nopw   %cs:0x0(%rax,%rax,1)
   0x4005b0 <_Z3quxP1C>:        push   %rbx
   0x4005b1 <_Z3quxP1C+1>:      mov    (%rdi),%rax
   0x4005b4 <_Z3quxP1C+4>:      test   %rax,%rax
   0x4005b7 <_Z3quxP1C+7>:      je     0x400460 <qux(C*)>
   0x4005bd <_Z3quxP1C+13>:     mov    (%rax),%rdi
   0x4005c0 <_Z3quxP1C+16>:     xor    %ebx,%ebx
   0x4005c2 <_Z3quxP1C+18>:     cmpl   $0x4,(%rdi)
   0x4005c5 <_Z3quxP1C+21>:     je     0x4005d8 <_Z3quxP1C+40>
   0x4005c7 <_Z3quxP1C+23>:     xor    %edi,%edi
   0x4005c9 <_Z3quxP1C+25>:     callq  0x4005a0 <baz(int)>
   0x4005ce <_Z3quxP1C+30>:     mov    %ebx,%edi
   0x4005d0 <_Z3quxP1C+32>:     pop    %rbx
   0x4005d1 <_Z3quxP1C+33>:     jmp    0x4005a0 <baz(int)>
   0x4005d3 <_Z3quxP1C+35>:     nopl   0x0(%rax,%rax,1)
   0x4005d8 <_Z3quxP1C+40>:     callq  0x400590 <foo(A*)>
   0x4005dd <_Z3quxP1C+45>:     xor    %edi,%edi
   0x4005df <_Z3quxP1C+47>:     movzbl %al,%ebx
   0x4005e2 <_Z3quxP1C+50>:     callq  0x4005a0 <baz(int)>
   0x4005e7 <_Z3quxP1C+55>:     mov    %ebx,%edi
   0x4005e9 <_Z3quxP1C+57>:     pop    %rbx
   0x4005ea <_Z3quxP1C+58>:     jmp    0x4005a0 <baz(int)>
   0x4005ec:    nopl   0x0(%rax)

Within GDB, this is where things go wrong:

top-gdb> bt 4
#0  find_pc_partial_function_gnu_ifunc (pc=4195728, name=0x7fffffffdd28, 
    address=0x7fffffffdd18, endaddr=0x7fffffffdd20, is_gnu_ifunc_p=0x0)
#1  0x0000000000553281 in find_pc_partial_function (pc=4195728, 
    name=0x7fffffffdd28, address=0x7fffffffdd18, endaddr=0x7fffffffdd20)
#2  0x00000000006b6aec in fill_in_stop_func (gdbarch=0x1249170, 
#3  0x00000000006baf13 in process_event_stop_test (ecs=0x7fffffffdcd0)

That pc value is actually the first address for foo(), which is what we want:

top-gdb> p/x pc
$22 = 0x400590

(Refer to my disassembly above to verify this.)

This code, which is in find_pc_partial_function_gnu_ifunc(), incorrectly
identifies this address, 0x400590, as belonging to qux:

  if (mapped_pc >= cache_pc_function_low
      && mapped_pc < cache_pc_function_high
      && section == cache_pc_function_section)
    goto return_cached_value;

top-gdb> p/x cache_pc_function_low
$30 = 0x400460
top-gdb> p/x cache_pc_function_high
$31 = 0x4005ec

Note that these high and low addresses include not only qux, but foo, baz,
main, and _start.  This is due to the fact that qux consists of two separate
parts separated by a lot of other code.

So far as I can tell, prologue analysis is not at fault here.

I'm now trying to figure out how this might be fixed.

Reply via email to