2011-04-02
How to use gdb
to call getpeername()
A commenter on my entry about the process state observability gap suggested an evil hack to recover
information about the destination of Unix domain sockets: use gdb on the
process to call getpeername()
on the socket and examine the result.
Because I am sometimes a crazy person, I decided to work out how to do
this in case I ever needed to do it for real.
Where this stuff hides in the GDB documentation was not obvious to me when I started. General information about gdb's expressions is in the section on printing and displaying data (section 10 in my GDB documentation, 'Examining data'). Information on calling functions in your program from gdb is in the section on altering execution (section 17 in my documentation, specifically 17.5).
First, getpeername()
has one of those odd APIs. It takes three
arguments; the file descriptor, a pointer to some sort of sockaddr
structure to store the name of the peer, and a pointer to where it can
read and write the length of the name buffer. Since we are trying to
use this on a Unix domain socket, we'd normally use a sockaddr_un
structure.
You will probably not be so lucky as to be dealing with a program
that already has an unused sockaddr_un
sitting around ready to be
hijacked for this, so we need to make our own. In fact, gdb probably won't
even know what a struct sockaddr_un
is, so we'll have to fake it.
(gdb takes its knowledge of structures from the program you're debugging, and most programs on most Linux systems don't have debugging information installed in the default configuration.)
This is the sort of situation where I present the gdb commands and then explain them, so:
print/x malloc(256)
- This will be our sockaddr buffer. It's
larger than a real
struct sockaddr_un
, but that's harmless and it saves us writing a program to determine exactly how big asockaddr_un
is. I'll assume that gdb calls what it prints here$1
, which is true if you're starting from a clean new gdb session. print/x malloc(4)
- This is for the length of the sockaddr buffer
(what the
getpeername()
manpage callsnamelen
). print {int} $2 = 256
- Set the length of our name buffer for the
kernel.
print getpeername(<fd>, $1, $2)
- If this prints anything besides
zero, something went wrong. Print out errno and work out what.
x/s $1+2
- Display the path part of our
sockaddr_un
buffer. I usex
instead ofprint
mostly because dumping memory is really what I'm doing and it feels right.
This is for a 32-bit Linux machine. If you're using a 64-bit machine you may need a different offset in the last command and maybe a different size for the second malloc.
This is not something you really want to do in a program that you want
to keep running unperturbed. Even if all goes well we've leaked some
memory, and there's a number of ways that something could go badly if
your timing with gdb is just unlucky enough (for example, the program
could be in the middle of a malloc()
or a free()
of its own; libc
is probably not written to be safely reentrant in this particular
situation).
(Having worked all of this out, I would feel very silly if I didn't write it down and then needed it later. I'm sure that it's all completely obvious to people who use gdb regularly.)