Peeking at the stack

We can take a peek into the process stack (technically, the stack of main()) in different ways. Here, we show two possibilities:

  • Automatically via the gstack(1) utility
  • Manually with the GDB debugger

Peek at the usermode stack, first, via gstack(1):

WARNING! Ubuntu users, you might face an issue here. At the time of writing (Ubuntu 18.04), gstack does not seem to be available for Ubuntu (and its alternative, pstack, does not work well either!). Please use the second method (via GDB), as follows.

As a quick example, we look up the stack of bash (the parameter is the PID of the process):

$ gstack 14654
#0 0x00007f3539ece7ea in waitpid () from /lib64/libc.so.6
#1 0x000056474b4b41d9 in waitchld.isra ()
#2 0x000056474b4b595d in wait_for ()
#3 0x000056474b4a5033 in execute_command_internal ()
#4 0x000056474b4a52c2 in execute_command ()
#5 0x000056474b48f252 in reader_loop ()
#6 0x000056474b48dd32 in main ()
$

The stack frame number appears on the left preceded by the # symbol; note that frame #0 is the top of the stack, (the lowest frame). Read the stack in a bottom-up fashion, that is, from frame #6 (the frame for the main() function) up to frame #0 (the frame for the waitpid() function). Also note that, if the process is multithreaded, gstack will show the stack of each thread.

Peek at the Usermode Stack, next, via GDB.

The GNU Debugger (GDB) is a renowned, very powerful debug tool (if you don't already use it, we highly recommend you learn how to; check out the link in the Further reading section). Here, we'll use GDB to attach to a process and, once attached, peek at its process stack. 

A small test C program, that makes several nested function calls, will serve as a good example. Essentially, the call graph will look as follows:

main() --> foo() --> bar() --> bar_is_now_closed() --> pause()

The pause(2) system call is a great example of a blocking call – it puts the calling process to sleep, waiting (or blocking) on an event; the event it's blocking upon here is the delivery of any signal to the process. (Patience; we'll learn more in Chapter 11, Signaling - Part I, and Chapter 12Signaling - Part II)

Here is the relevant code (ch2/stacker.c):

static void bar_is_now_closed(void)
{
printf("In function %s "
" (bye, pl go '~/' now). ", __FUNCTION__);
printf(" Now blocking on pause()... "
" Connect via GDB's 'attach' and then issue the 'bt' command"
" to view the process stack ");
pause(); /*process blocks here until it receives a signal */
}
static void bar(void)
{
printf("In function %s ", __FUNCTION__);
bar_is_now_closed();
}
static void foo(void)
{
printf("In function %s ", __FUNCTION__);
bar();
}
int main(int argc, char **argv)
{
printf("In function %s ", __FUNCTION__);
foo();
exit (EXIT_SUCCESS);
}

Note that, for GDB to see the symbols (names of functions, variables, line numbers), one must compile the code with the -g switch (produces debug information).

Now, we run the process in the background:

$ ./stacker_dbg &
[2] 28957
In function main
In function foo
In function bar
In function bar_is_now_closed
(bye, pl go '~/' now).
Now blocking on pause()...
Connect via GDB's 'attach' and then issue the 'bt' command to view the process stack
$

Next, open GDB; within GDB, attach to the process (the PID is displayed in the preceding code), and view its stack with the backtracebt) command:

$ gdb --quiet
(gdb) attach 28957 # parameter to 'attach' is the PID of the process to attach to
Attaching to process 28957
Reading symbols from <...>/Hands-on-System-Programming-with-Linux/ch2/stacker_dbg...done.
Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.26.so.debug...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug/usr/lib64/ld-2.26.so.debug...done.
done.
0x00007fce204143b1 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:30
30 return SYSCALL_CANCEL (pause);
(gdb) bt
#0 0x00007fce204143b1 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:30
#1 0x00000000004007ce in bar_is_now_closed () at stacker.c:31
#2 0x00000000004007ee in bar () at stacker.c:36
#3 0x000000000040080e in foo () at stacker.c:41
#4 0x0000000000400839 in main (argc=1, argv=0x7ffca9ac5ff8) at stacker.c:47
(gdb)
On Ubuntu, due to security, GDB will not allow one to attach to any process; one can overcome this by running GDB as root; then it works well.

How about looking up the same process via gstack (at the time of writing, Ubuntu users, you're out of luck). Here it is on a Fedora 27 box:

$ gstack 28957
#0 0x00007fce204143b1 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:30
#1 0x00000000004007ce in bar_is_now_closed () at stacker.c:31
#2 0x00000000004007ee in bar () at stacker.c:36
#3 0x000000000040080e in foo () at stacker.c:41
#4 0x0000000000400839 in main (argc=1, argv=0x7ffca9ac5ff8) at stacker.c:47
$
Guess what? It turns out that gstack is really a wrapper shell script that invokes GDB in a non-interactive fashion and it issues the very same backtrace command we just used! 
As a quick learning exercise, check out the gstack script.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset