0

So, I was working with a typical project of mine. For that I wrote the following program:

/***************************demo.c*************************/
#include <stdio.h>
#include <unistd.h>
void foo(int x)
{
    getpid();
    open();
    getpid();
    
}
int main()
{
    int x;
    foo(10);
}

and I statically compiled it as follows:

$gcc -g -static demo.c -o demo

I tried to stack trace using gdb:

(gdb) list
1       /***************************demo.c*************************/
2       #include <stdio.h>
3       #include <unistd.h>
4       void foo(int x)
5       {
6           getpid();
7           open();
8           getpid();
9
10      }
(gdb) 
11      int main()
12      {
13          int x;
14          foo(10);
15      }
(gdb) start
Temporary breakpoint 1 at 0x401773: file demo.c, line 14.
Starting program: /home/abhishek/Documents/Security_Project/bin/backend_v2/demo 

Temporary breakpoint 1, main () at demo.c:14
14          foo(10);
(gdb) s
foo (x=10) at demo.c:6
6           getpid();
(gdb) backtrace
#0  foo (x=10) at demo.c:6
#1  0x000000000040177d in main () at demo.c:14
(gdb) 

But to my astonishment, why isn't something like the following shown?

#   0x0000000000401759 in foo (x=10) at demo.c:6

I tried to use ptrace(PTRACE_PEEKDATA,... and ptrace(PTRACE_GETREGS,... to get hold of rip, and rbp and then peeking into memory of attached child process as follows:

long return_addr=ptrace(PTRACE_PEEKDATA, pid,regs.rbp+8,0);
    long next_rbp=ptrace(PTRACE_PEEKDATA, pid, regs.rbp,0);
    while(next_rbp>0){
        // printf("rbp  = 0x%lx", next_rbp);
        // getchar();
        printf("0x%lx\t", return_addr);
        addr_on_stk.insert(return_addr);
        return_addr=ptrace(PTRACE_PEEKDATA, pid, next_rbp+8,0);
        next_rbp=ptrace(PTRACE_PEEKDATA, pid, next_rbp,0);
    }

To which I get the following output during the first iteration stopping at the first system call, (ignoring the initial 14 syscall made to setup the program, I mean stopping it at getpid syscall)

Trace back of the addresses on stack is as follows: 
0x44673b
0x40177d
0x401bba

apparently what I see is :

Dump of assembler code for function getpid:
   0x0000000000446730 <+0>:     endbr64 
   0x0000000000446734 <+4>:     mov    $0x27,%eax
   0x0000000000446739 <+9>:     syscall 
   0x000000000044673b <+11>:    ret  <----------------------- first address  
End of assembler dump.
Dump of assembler code for function main:
   0x000000000040176b <+0>:     endbr64 
   0x000000000040176f <+4>:     push   %rbp
   0x0000000000401770 <+5>:     mov    %rsp,%rbp
   0x0000000000401773 <+8>:     mov    $0xa,%edi
   0x0000000000401778 <+13>:    call   0x401745 <foo>
   0x000000000040177d <+18>:    mov    $0x0,%eax     <-------------- second address
   0x0000000000401782 <+23>:    pop    %rbp
   0x0000000000401783 <+24>:    ret    
End of assembler dump.
   0x401bba <__libc_start_call_main+106>:       mov    %eax,%edi
(gdb) disassemble __libc_start_call_main 
Dump of assembler code for function __libc_start_call_main:
   0x0000000000401b50 <+0>:     push   %rax
   0x0000000000401b51 <+1>:     pop    %rax
   0x0000000000401b52 <+2>:     sub    $0x98,%rsp
   0x0000000000401b59 <+9>:     mov    %rdi,0x8(%rsp)
   0x0000000000401b5e <+14>:    lea    0x20(%rsp),%rdi
   0x0000000000401b63 <+19>:    mov    %esi,0x14(%rsp)
   0x0000000000401b67 <+23>:    mov    %rdx,0x18(%rsp)
   0x0000000000401b6c <+28>:    call   0x409ed0 <_setjmp>
   0x0000000000401b71 <+33>:    endbr64 
   0x0000000000401b75 <+37>:    test   %eax,%eax
   0x0000000000401b77 <+39>:    jne    0x401bc1 <__libc_start_call_main+113>
   0x0000000000401b79 <+41>:    mov    %fs:0x300,%rax
   0x0000000000401b82 <+50>:    mov    %rax,0x68(%rsp)
   0x0000000000401b87 <+55>:    mov    %fs:0x2f8,%rax
   0x0000000000401b90 <+64>:    mov    %rax,0x70(%rsp)
   0x0000000000401b95 <+69>:    lea    0x20(%rsp),%rax
   0x0000000000401b9a <+74>:    mov    %rax,%fs:0x300
   0x0000000000401ba3 <+83>:    mov    0xca686(%rip),%rdx        # 0x4cc230 <environ>
   0x0000000000401baa <+90>:    mov    0x14(%rsp),%edi
   0x0000000000401bae <+94>:    mov    0x18(%rsp),%rsi
   0x0000000000401bb3 <+99>:    mov    0x8(%rsp),%rax
   0x0000000000401bb8 <+104>:   call   *%rax
   0x0000000000401bba <+106>:   mov    %eax,%edi   <------------------------third address
   0x0000000000401bbc <+108>:   call   0x40aa80 <exit>
   0x0000000000401bc1 <+113>:   call   0x414520 <__nptl_deallocate_tsd>
   0x0000000000401bc6 <+118>:   lock decl 0xc3b33(%rip)        # 0x4c5700 <__nptl_nthreads>
   0x0000000000401bcd <+125>:   sete   %al
   0x0000000000401bd0 <+128>:   test   %al,%al
   0x0000000000401bd2 <+130>:   jne    0x401be8 <__libc_start_call_main+152>
   0x0000000000401bd4 <+132>:   mov    $0x3c,%edx
   0x0000000000401bd9 <+137>:   nopl   0x0(%rax)
   0x0000000000401be0 <+144>:   xor    %edi,%edi
   0x0000000000401be2 <+146>:   mov    %edx,%eax
   0x0000000000401be4 <+148>:   syscall 
   0x0000000000401be6 <+150>:   jmp    0x401be0 <__libc_start_call_main+144>
   0x0000000000401be8 <+152>:   xor    %edi,%edi
   0x0000000000401bea <+154>:   jmp    0x401bbc <__libc_start_call_main+108>
End of assembler dump.
(gdb) 

All the return addresses foo is found.

My question is when the getpid syscall is intercepted, it is down in the glibc wrapper of getpid as shown. But why isn't the function foo's frame formally placed on the stack?

[I mean, by the time is syscall is intercepted by PTRACE, push rbp instruction should have been performed and rbp should point to the old rbp on stack and just below it (i.e. rbp+8), shall have the return address of getpid for foo().]

Abhishek Ghosh
  • 597
  • 7
  • 18

1 Answers1

1

But to my astonishment, why isn't something like the following shown?
# 0x0000000000401759 in foo (x=10) at demo.c:61

That is a GDB artifact -- it doesn't show the pc for the frame if that frame has debug info and and you are stopped at the beginning of the line.

If you do catch syscall getpid and do backtrace from there, you would see:

(gdb) bt
#0  0x000000000044353b in getpid ()
#1  0x0000000000401665 in foo (x=10) at demo.c:5
#2  0x0000000000401685 in main () at demo.c:13

Compare with the output from lldb:

(lldb) bt
* thread #1, name = 'a.out', stop reason = breakpoint 1.1
  * frame #0: 0x0000000000401660 a.out`foo(x=10) at demo.c:5:5
    frame #1: 0x0000000000401685 a.out`main at demo.c:13:5
    frame #2: 0x0000000000401a0a a.out`__libc_start_call_main + 106
    frame #3: 0x00000000004030b7 a.out`__libc_start_main_impl + 2327
    frame #4: 0x0000000000401555 a.out`_start + 37

Also, if you compile your code without -g and break on foo, you would see this:

Breakpoint 1, 0x0000000000401659 in foo ()
(gdb) bt
#0  0x0000000000401659 in foo ()
#1  0x0000000000401685 in main ()

But why is the function foo's frame formally placed on the stack?

I think you mean: why isn't foos frame placed on the stack.

I mean, by the time is syscall is intercepted by PTRACE, push rbp instruction should have been performed and rbp should point to the old rbp on stack and just below it (i.e. rbp+8), shall have the return address of getpid for foo()

What you expect would have happened IFF the getpid wrapper used a frame pointer (i.e. had the push %rbp; mov %rsp,%rbp in its prolog). But it doesn't (as is clear from disassembly), and so the rbp value just before the actual getpid system call is the same as it was in foo.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • 2
    Small correction to the first paragraph. GDB doesn't show the `$pc` value for any frame, if the `$pc` value in that frame is considered to be at the start of a line. So when GDB says `demo.c:61` and no `$pc` is show you can know that you are located at the start of line 61. When a `$pc` value is shown then that indicates we are part way through a line. You are correct though, that is a frame has no debug information then the `$pc` will always be displayed (as GDB can't know if you are at the start of a line). – Andrew Nov 20 '22 at 11:24
  • 1
    @Andrew Thanks for the correction. I never looked at why the `$pc` is sometimes not displayed, and only guessed the reason. – Employed Russian Nov 20 '22 at 17:15
  • @EmployedRussian yes, I meant "why isn't foo..." sorry for the typo – Abhishek Ghosh Nov 20 '22 at 17:49