[PATCH] Add extra 'info os' information types for Linux

Kwok Cheung Yeung
2011-10-12T18:29:20Z

[-- Attachment #1: Type: text/plain, Size: 2124 bytes --]

This patch greatly extends the OS information types available to the user using
the 'info os' command in Linux. Since the OS info backend is now unified, this
information can be obtained from both local and remote (gdbserver) targets.

The new information types are:

procgroups - Process groups
files - File descriptors
sockets - Internet-domain sockets
shm - Shared memory regions
semaphores - Semaphores
msg - Message queues
modules - Loaded kernel modules

They all work in the same manner as the existing 'processes' and 'threads'
information types - by pulling the information from the /proc filesystem and
formatting it into a table.

This facility is tested using a program which creates file descriptors, sockets
and IPC structures with specific port numbers/targets, and the test script then
looks for these in the output of 'info os'.

The documentation is also updated regarding these information types.

Kwok Cheung Yeung

ChangeLog:

gdb/
* common/linux-osdata.c (compare_processes): Auxiliary function for
linux_xfer_osdata_processgroups.
(linux_xfer_osdata_processgroups): New function to
look up process groups.
(linux_xfer_osdata_fds): New function to look up file descriptors.
(format_socket_state, print_sockets): Auxiliary functions for
linux_xfer_osdata_isockets.
(union socket_addr): New union used to avoid strict-aliasing problems.
(linux_xfer_osdata_isockets): New function to look up internet sockets.
(time_from_int, group_from_gid): New convenience functions for
converting between data types.
(linux_xfer_osdata_shm): New function to look up shared memory for IPC.
(linux_xfer_osdata_sem): New function to look up semaphores for IPC.
(linux_xfer_osdata_msg): New function to look up message queues for
IPC.
(linux_xfer_osdata_modules): New function to look up loaded kernel
modules.
(osdata_table): Add new entries.

gdb/doc/
* gdb.texinfo (Operating System Auxilliary Information): Document new
'info os' commands.

gdb/testsuite/
* gdb.base/info-os.exp: New test to exercise the 'info os *' commands.
* gdb.base/info-os.c: New test program used by info-os.exp.

[-- Attachment #2: os-awareness.diff --]
[-- Type: text/plain, Size: 39244 bytes --]

Index: gdb/common/linux-osdata.c
===================================================================
RCS file: /cvs/src/src/gdb/common/linux-osdata.c,v
retrieving revision 1.2
diff -u -p -r1.2 linux-osdata.c
--- gdb/common/linux-osdata.c 26 Aug 2011 18:58:04 -0000 1.2
+++ gdb/common/linux-osdata.c 12 Oct 2011 18:19:25 -0000
@@ -396,6 +396,146 @@ linux_xfer_osdata_processes (gdb_byte *r
return len;
}

+static int
+compare_processes (const void *process1, const void *process2)
+{
+ pid_t pid1 = *((pid_t *) process1);
+ pid_t pid2 = *((pid_t *) process2);
+ pid_t pgid1 = *((pid_t *) process1 + 1);
+ pid_t pgid2 = *((pid_t *) process2 + 1);
+
+ /* Sort by PGID. */
+ if (pgid1 < pgid2)
+ return -1;
+ else if (pgid1 > pgid2)
+ return 1;
+ else
+ {
+ /* Process group leaders always come first, else sort by PID. */
+ if (pid1 == pgid1)
+ return -1;
+ else if (pid2 == pgid2)
+ return 1;
+ else if (pid1 < pid2)
+ return -1;
+ else if (pid1 > pid2)
+ return 1;
+ else
+ return 0;
+ }
+}
+
+static LONGEST
+linux_xfer_osdata_processgroups (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ /* We make the process list snapshot when the object starts to be read. */
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ DIR *dirp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"process groups\">\n");
+
+ dirp = opendir ("/proc");
+ if (dirp)
+ {
+ struct dirent *dp;
+ const size_t list_block_size = 512;
+ pid_t *process_list = (pid_t *) xmalloc (list_block_size * 2 * sizeof (pid_t));
+ size_t process_count = 0;
+ size_t i;
+
+ /* Build list consisting of PIDs followed by their associated PGID. */
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ pid_t pid, pgid;
+
+ if (!isdigit (dp->d_name[0])
+ || NAMELEN (dp) > sizeof ("4294967295") - 1)
+ continue;
+
+ sscanf (dp->d_name, "%d", &pid);
+ pgid = getpgid (pid);
+
+ if (pgid > 0)
+ {
+ process_list[2 * process_count] = pid;
+ process_list[2 * process_count + 1] = pgid;
+ ++process_count;
+
+ /* Increase the size of the list if necessary. */
+ if (process_count % list_block_size == 0)
+ process_list = (pid_t *) xrealloc (
+ process_list,
+ (process_count + list_block_size) * 2 * sizeof (pid_t));
+ }
+ }
+
+ closedir (dirp);
+
+ /* Sort the process list. */
+ qsort (process_list, process_count, 2 * sizeof (pid_t), compare_processes);
+
+ for (i = 0; i < process_count; ++i)
+ {
+ pid_t pid = process_list[2 * i];
+ pid_t pgid = process_list[2 * i + 1];
+ char leader_command[32];
+ char *command_line;
+
+ command_from_pid (leader_command, sizeof (leader_command), pgid);
+ command_line = commandline_from_pid (pid);
+
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"pgid\">%d</column>"
+ "<column name=\"leader command\">%s</column>"
+ "<column name=\"pid\">%d</column>"
+ "<column name=\"command line\">%s</column>"
+ "</item>",
+ pgid,
+ leader_command,
+ pid,
+ command_line ? command_line : "");
+
+ xfree (command_line);
+ }
+
+ xfree (process_list);
+ }
+
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
static LONGEST
linux_xfer_osdata_threads (gdb_byte *readbuf,
ULONGEST offset, LONGEST len)
@@ -507,13 +647,840 @@ linux_xfer_osdata_threads (gdb_byte *rea
return len;
}

+static LONGEST
+linux_xfer_osdata_fds (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ /* We make the process list snapshot when the object starts to be read. */
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ DIR *dirp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"files\">\n");
+
+ dirp = opendir ("/proc");
+ if (dirp)
+ {
+ struct dirent *dp;
+
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ struct stat statbuf;
+ char procentry[sizeof ("/proc/4294967295")];
+
+ if (!isdigit (dp->d_name[0])
+ || NAMELEN (dp) > sizeof ("4294967295") - 1)
+ continue;
+
+ sprintf (procentry, "/proc/%s", dp->d_name);
+ if (stat (procentry, &statbuf) == 0
+ && S_ISDIR (statbuf.st_mode))
+ {
+ char *pathname;
+ DIR *dirp2;
+ pid_t pid;
+ char command[32];
+
+ pid = atoi (dp->d_name);
+ command_from_pid (command, sizeof (command), pid);
+
+ pathname = xstrprintf ("/proc/%s/fd", dp->d_name);
+ dirp2 = opendir (pathname);
+
+ if (dirp2)
+ {
+ struct dirent *dp2;
+
+ while ((dp2 = readdir (dirp2)) != NULL)
+ {
+#if 0
+ struct stat statbuf;
+ stat (dp2->d_name, &statbuf);
+#endif
+ char *fdname;
+ char buf[1000];
+ ssize_t rslt;
+
+ if (!isdigit (dp2->d_name[0]))
+ continue;
+
+ fdname = xstrprintf ("%s/%s", pathname, dp2->d_name);
+ rslt = readlink (fdname, buf, 1000);
+ if (rslt >= 0)
+ buf[rslt] = '\0';
+
+#if 0
+ printf_unfiltered ("readlink returned %d, %s\n", rslt, buf);
+#endif
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"pid\">%s</column>"
+ "<column name=\"command\">%s</column>"
+ "<column name=\"file descriptor\">%s</column>"
+ "<column name=\"name\">%s</column>"
+ "</item>",
+ dp->d_name,
+ command,
+ dp2->d_name,
+ (rslt >= 0 ? buf : dp2->d_name));
+ }
+
+ closedir (dirp2);
+ }
+
+ xfree (pathname);
+ }
+ }
+
+ closedir (dirp);
+ }
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static const char *
+format_socket_state (unsigned char state)
+{
+ /* Copied from include/net/tcp_states.h in the Linux kernel sources. */
+ enum {
+ TCP_ESTABLISHED = 1,
+ TCP_SYN_SENT,
+ TCP_SYN_RECV,
+ TCP_FIN_WAIT1,
+ TCP_FIN_WAIT2,
+ TCP_TIME_WAIT,
+ TCP_CLOSE,
+ TCP_CLOSE_WAIT,
+ TCP_LAST_ACK,
+ TCP_LISTEN,
+ TCP_CLOSING
+ };
+
+ switch (state)
+ {
+ case TCP_ESTABLISHED:
+ return "ESTABLISHED";
+ case TCP_SYN_SENT:
+ return "SYN_SENT";
+ case TCP_SYN_RECV:
+ return "SYN_RECV";
+ case TCP_FIN_WAIT1:
+ return "FIN_WAIT1";
+ case TCP_FIN_WAIT2:
+ return "FIN_WAIT2";
+ case TCP_TIME_WAIT:
+ return "TIME_WAIT";
+ case TCP_CLOSE:
+ return "CLOSE";
+ case TCP_CLOSE_WAIT:
+ return "CLOSE_WAIT";
+ case TCP_LAST_ACK:
+ return "LAST_ACK";
+ case TCP_LISTEN:
+ return "LISTEN";
+ case TCP_CLOSING:
+ return "CLOSING";
+ default:
+ return "(unknown)";
+ }
+}
+
+union socket_addr
+ {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ };
+
+static void
+print_sockets (unsigned short family, int tcp, struct buffer *buffer)
+{
+ const char *proc_file;
+ FILE *fp;
+
+ if (family == AF_INET)
+ proc_file = tcp ? "/proc/net/tcp" : "/proc/net/udp";
+ else if (family == AF_INET6)
+ proc_file = tcp ? "/proc/net/tcp6" : "/proc/net/udp6";
+ else
+ return;
+
+ fp = fopen (proc_file, "r");
+ if (fp)
+ {
+ char buf[8192];
+
+ do
+ {
+ if (fgets (buf, sizeof (buf), fp))
+ {
+ uid_t uid;
+ unsigned long tlen, inode;
+ int sl, timeout;
+ unsigned int local_port, remote_port, state, txq, rxq, trun, retn;
+ char local_address[NI_MAXHOST], remote_address[NI_MAXHOST], extra[512];
+ int result;
+
+ result = sscanf (buf,
+ "%d: %33[0-9A-F]:%X %33[0-9A-F]:%X %X %X:%X %X:%lX %X %d %d %lu %512s\n",
+ &sl,
+ local_address, &local_port,
+ remote_address, &remote_port,
+ &state,
+ &txq, &rxq,
+ &trun, &tlen,
+ &retn,
+ &uid,
+ &timeout,
+ &inode,
+ extra);
+
+ if (result == 15)
+ {
+ union socket_addr locaddr, remaddr;
+ size_t addr_size;
+ char user[UT_NAMESIZE];
+ char local_service[NI_MAXSERV], remote_service[NI_MAXSERV];
+
+ if (family == AF_INET)
+ {
+ sscanf (local_address, "%X", &locaddr.sin.sin_addr.s_addr);
+ sscanf (remote_address, "%X", &remaddr.sin.sin_addr.s_addr);
+
+ locaddr.sin.sin_port = htons (local_port);
+ remaddr.sin.sin_port = htons (remote_port);
+
+ addr_size = sizeof (struct sockaddr_in);
+ }
+ else
+ {
+ sscanf (local_address, "%8X%8X%8X%8X",
+ locaddr.sin6.sin6_addr.s6_addr32,
+ locaddr.sin6.sin6_addr.s6_addr32 + 1,
+ locaddr.sin6.sin6_addr.s6_addr32 + 2,
+ locaddr.sin6.sin6_addr.s6_addr32 + 3);
+ sscanf (remote_address, "%8X%8X%8X%8X",
+ remaddr.sin6.sin6_addr.s6_addr32,
+ remaddr.sin6.sin6_addr.s6_addr32 + 1,
+ remaddr.sin6.sin6_addr.s6_addr32 + 2,
+ remaddr.sin6.sin6_addr.s6_addr32 + 3);
+
+ locaddr.sin6.sin6_port = htons (local_port);
+ remaddr.sin6.sin6_port = htons (remote_port);
+
+ locaddr.sin6.sin6_flowinfo = 0;
+ remaddr.sin6.sin6_flowinfo = 0;
+ locaddr.sin6.sin6_scope_id = 0;
+ remaddr.sin6.sin6_scope_id = 0;
+
+ addr_size = sizeof (struct sockaddr_in6);
+ }
+
+ locaddr.sa.sa_family = remaddr.sa.sa_family = family;
+
+ result = getnameinfo (&locaddr.sa, addr_size,
+ local_address, sizeof (local_address),
+ local_service, sizeof (local_service),
+ NI_NUMERICHOST | NI_NUMERICSERV
+ | (tcp ? 0 : NI_DGRAM));
+ if (result)
+ continue;
+
+ result = getnameinfo (&remaddr.sa, addr_size,
+ remote_address, sizeof (remote_address),
+ remote_service, sizeof (remote_service),
+ NI_NUMERICHOST | NI_NUMERICSERV
+ | (tcp ? 0 : NI_DGRAM));
+ if (result)
+ continue;
+
+ user_from_uid (user, sizeof (user), uid);
+
+ buffer_xml_printf (
+ buffer,
+ "<item>"
+ "<column name=\"local address\">%s</column>"
+ "<column name=\"local port\">%s</column>"
+ "<column name=\"remote address\">%s</column>"
+ "<column name=\"remote port\">%s</column>"
+ "<column name=\"state\">%s</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"family\">%s</column>"
+ "<column name=\"protocol\">%s</column>"
+ "</item>",
+ local_address,
+ local_service,
+ remote_address,
+ remote_service,
+ format_socket_state (state),
+ user,
+ (family == AF_INET) ? "INET" : "INET6",
+ tcp ? "STREAM" : "DGRAM");
+ }
+ }
+ }
+ while (!feof (fp));
+
+ fclose (fp);
+ }
+}
+
+static LONGEST
+linux_xfer_osdata_isockets (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"I sockets\">\n");
+
+ print_sockets (AF_INET, 1, &buffer);
+ print_sockets (AF_INET, 0, &buffer);
+ print_sockets (AF_INET6, 1, &buffer);
+ print_sockets (AF_INET6, 0, &buffer);
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static void
+time_from_int (char *time, int maxlen, int seconds)
+{
+ if (!seconds)
+ time[0] = '\0';
+ else
+ {
+ time_t t = (time_t) seconds;
+
+ strncpy (time, ctime (&t), maxlen);
+ time[maxlen - 1] = '\0';
+ }
+}
+
+static void
+group_from_gid (char *group, int maxlen, gid_t gid)
+{
+ struct group *grentry = getgrgid (gid);
+
+ if (grentry)
+ {
+ strncpy (group, grentry->gr_name, maxlen);
+ group[maxlen - 1] = '\0'; /* Ensure that the group name is null-terminated. */
+ }
+ else
+ group[0] = '\0';
+}
+
+static LONGEST
+linux_xfer_osdata_shm (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ FILE *fp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"shared memory\">\n");
+
+ fp = fopen ("/proc/sysvipc/shm", "r");
+ if (fp)
+ {
+ char buf[8192];
+
+ do
+ {
+ if (fgets (buf, sizeof (buf), fp))
+ {
+ key_t key;
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ pid_t cpid, lpid;
+ int shmid, size, nattch, atime, dtime, ctime;
+ unsigned int perms;
+ int items_read;
+
+ items_read = sscanf (buf,
+ "%d %d %o %d %d %d %d %u %u %u %u %d %d %d",
+ &key, &shmid, &perms, &size,
+ &cpid, &lpid,
+ &nattch,
+ &uid, &gid, &cuid, &cgid,
+ &atime, &dtime, &ctime);
+
+ if (items_read == 14)
+ {
+ char user[UT_NAMESIZE], group[UT_NAMESIZE];
+ char cuser[UT_NAMESIZE], cgroup[UT_NAMESIZE];
+ char ccmd[32], lcmd[32];
+ char atime_str[32], dtime_str[32], ctime_str[32];
+
+ user_from_uid (user, sizeof (user), uid);
+ group_from_gid (group, sizeof (group), gid);
+ user_from_uid (cuser, sizeof (cuser), cuid);
+ group_from_gid (cgroup, sizeof (cgroup), cgid);
+
+ command_from_pid (ccmd, sizeof (ccmd), cpid);
+ command_from_pid (lcmd, sizeof (lcmd), lpid);
+
+ time_from_int (atime_str, sizeof (atime_str), atime);
+ time_from_int (dtime_str, sizeof (dtime_str), dtime);
+ time_from_int (ctime_str, sizeof (ctime_str), ctime);
+
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"key\">%d</column>"
+ "<column name=\"shmid\">%d</column>"
+ "<column name=\"permissions\">%o</column>"
+ "<column name=\"size\">%d</column>"
+ "<column name=\"creator command\">%s</column>"
+ "<column name=\"last op. command\">%s</column>"
+ "<column name=\"num attached\">%d</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"group\">%s</column>"
+ "<column name=\"creator user\">%s</column>"
+ "<column name=\"creator group\">%s</column>"
+ "<column name=\"last shmat() time\">%s</column>"
+ "<column name=\"last shmdt() time\">%s</column>"
+ "<column name=\"last shmctl() time\">%s</column>"
+ "</item>",
+ key,
+ shmid,
+ perms,
+ size,
+ ccmd,
+ lcmd,
+ nattch,
+ user,
+ group,
+ cuser,
+ cgroup,
+ atime_str,
+ dtime_str,
+ ctime_str);
+ }
+ }
+ }
+ while (!feof (fp));
+
+ fclose (fp);
+ }
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static LONGEST
+linux_xfer_osdata_sem (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ FILE *fp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"semaphores\">\n");
+
+ fp = fopen ("/proc/sysvipc/sem", "r");
+ if (fp)
+ {
+ char buf[8192];
+
+ do
+ {
+ if (fgets (buf, sizeof (buf), fp))
+ {
+ key_t key;
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ unsigned int perms, nsems;
+ int semid, otime, ctime;
+ int items_read;
+
+ items_read = sscanf (buf,
+ "%d %d %o %u %d %d %d %d %d %d",
+ &key, &semid, &perms, &nsems,
+ &uid, &gid, &cuid, &cgid,
+ &otime, &ctime);
+
+ if (items_read == 10)
+ {
+ char user[UT_NAMESIZE], group[UT_NAMESIZE];
+ char cuser[UT_NAMESIZE], cgroup[UT_NAMESIZE];
+ char otime_str[32], ctime_str[32];
+
+ user_from_uid (user, sizeof (user), uid);
+ group_from_gid (group, sizeof (group), gid);
+ user_from_uid (cuser, sizeof (cuser), cuid);
+ group_from_gid (cgroup, sizeof (cgroup), cgid);
+
+ time_from_int (otime_str, sizeof (otime_str), otime);
+ time_from_int (ctime_str, sizeof (ctime_str), ctime);
+
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"key\">%d</column>"
+ "<column name=\"semid\">%d</column>"
+ "<column name=\"permissions\">%o</column>"
+ "<column name=\"num semaphores\">%u</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"group\">%s</column>"
+ "<column name=\"creator user\">%s</column>"
+ "<column name=\"creator group\">%s</column>"
+ "<column name=\"last semop() time\">%s</column>"
+ "<column name=\"last semctl() time\">%s</column>"
+ "</item>",
+ key,
+ semid,
+ perms,
+ nsems,
+ user,
+ group,
+ cuser,
+ cgroup,
+ otime_str,
+ ctime_str);
+ }
+ }
+ }
+ while (!feof (fp));
+
+ fclose (fp);
+ }
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static LONGEST
+linux_xfer_osdata_msg (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ FILE *fp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"message queues\">\n");
+
+ fp = fopen ("/proc/sysvipc/msg", "r");
+ if (fp)
+ {
+ char buf[8192];
+
+ do
+ {
+ if (fgets (buf, sizeof (buf), fp))
+ {
+ key_t key;
+ pid_t lspid, lrpid;
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ unsigned int perms, cbytes, qnum;
+ int msqid, stime, rtime, ctime;
+ int items_read;
+
+ items_read = sscanf (buf,
+ "%d %d %o %u %u %d %d %d %d %d %d %d %d %d",
+ &key, &msqid, &perms, &cbytes, &qnum,
+ &lspid, &lrpid, &uid, &gid, &cuid, &cgid,
+ &stime, &rtime, &ctime);
+
+ if (items_read == 14)
+ {
+ char user[UT_NAMESIZE], group[UT_NAMESIZE];
+ char cuser[UT_NAMESIZE], cgroup[UT_NAMESIZE];
+ char lscmd[32], lrcmd[32];
+ char stime_str[32], rtime_str[32], ctime_str[32];
+
+ user_from_uid (user, sizeof (user), uid);
+ group_from_gid (group, sizeof (group), gid);
+ user_from_uid (cuser, sizeof (cuser), cuid);
+ group_from_gid (cgroup, sizeof (cgroup), cgid);
+
+ command_from_pid (lscmd, sizeof (lscmd), lspid);
+ command_from_pid (lrcmd, sizeof (lrcmd), lrpid);
+
+ time_from_int (stime_str, sizeof (stime_str), stime);
+ time_from_int (rtime_str, sizeof (rtime_str), rtime);
+ time_from_int (ctime_str, sizeof (ctime_str), ctime);
+
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"key\">%d</column>"
+ "<column name=\"msqid\">%d</column>"
+ "<column name=\"permissions\">%o</column>"
+ "<column name=\"num used bytes\">%u</column>"
+ "<column name=\"num messages\">%u</column>"
+ "<column name=\"last msgsnd() command\">%s</column>"
+ "<column name=\"last msgrcv() command\">%s</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"group\">%s</column>"
+ "<column name=\"creator user\">%s</column>"
+ "<column name=\"creator group\">%s</column>"
+ "<column name=\"last msgsnd() time\">%s</column>"
+ "<column name=\"last msgrcv() time\">%s</column>"
+ "<column name=\"last msgctl() time\">%s</column>"
+ "</item>",
+ key,
+ msqid,
+ perms,
+ cbytes,
+ qnum,
+ lscmd,
+ lrcmd,
+ user,
+ group,
+ cuser,
+ cgroup,
+ stime_str,
+ rtime_str,
+ ctime_str);
+ }
+ }
+ }
+ while (!feof (fp));
+
+ fclose (fp);
+ }
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static LONGEST
+linux_xfer_osdata_modules (gdb_byte *readbuf,
+ ULONGEST offset, LONGEST len)
+{
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct buffer buffer;
+
+ if (offset == 0)
+ {
+ FILE *fp;
+
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"modules\">\n");
+
+ fp = fopen ("/proc/modules", "r");
+ if (fp)
+ {
+ char buf[8192];
+
+ do
+ {
+ if (fgets (buf, sizeof (buf), fp))
+ {
+ char name[64], dependencies[256], status[16];
+ unsigned int size;
+ unsigned long long address;
+ int uses;
+ int items_read;
+
+ items_read = sscanf (buf,
+ "%64s %d %d %256s %16s 0x%llx",
+ name, &size, &uses,
+ dependencies, status, &address);
+
+ if (items_read == 6)
+ {
+ char address_str[20];
+
+ snprintf (address_str, 20, "%llx", address);
+ buffer_xml_printf (
+ &buffer,
+ "<item>"
+ "<column name=\"name\">%s</column>"
+ "<column name=\"size\">%u</column>"
+ "<column name=\"num uses\">%d</column>"
+ "<column name=\"dependencies\">%s</column>"
+ "<column name=\"status\">%s</column>"
+ "<column name=\"address\">%s</column>"
+ "</item>",
+ name,
+ size,
+ uses,
+ dependencies,
+ status,
+ address_str);
+ }
+ }
+ }
+ while (!feof (fp));
+
+ fclose (fp);
+ }
+
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the buffer. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
struct osdata_type {
char *type;
char *description;
LONGEST (*getter) (gdb_byte *readbuf, ULONGEST offset, LONGEST len);
} osdata_table[] = {
{ "processes", "Listing of all processes", linux_xfer_osdata_processes },
+ { "procgroups", "Listing of all process groups", linux_xfer_osdata_processgroups },
{ "threads", "Listing of all threads", linux_xfer_osdata_threads },
+ { "files", "Listing of all file descriptors", linux_xfer_osdata_fds },
+ { "sockets", "Listing of all internet-domain sockets", linux_xfer_osdata_isockets },
+ { "shm", "Listing of all shared-memory regions", linux_xfer_osdata_shm },
+ { "semaphores", "Listing of all semaphores", linux_xfer_osdata_sem },
+ { "msg", "Listing of all message queues", linux_xfer_osdata_msg },
+ { "modules", "Listing of all loaded kernel modules", linux_xfer_osdata_modules },
{ NULL, NULL, NULL }
};

Index: gdb/doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.877
diff -u -p -r1.877 gdb.texinfo
--- gdb/doc/gdb.texinfo 12 Oct 2011 15:55:04 -0000 1.877
+++ gdb/doc/gdb.texinfo 12 Oct 2011 18:19:31 -0000
@@ -8916,22 +8916,105 @@ an unrecognized tag.
@end table

On some targets, @value{GDBN} can access operating-system-specific information
-and display it to user, without interpretation. For remote targets,
-this functionality depends on the remote stub's support of the
+and display it to user. The types of information available will differ
+depending on the type of operating system running on the target. The
+mechanism used to fetch the data is described in
+@ref{Operating System Information}. For remote targets, this
+functionality depends on the remote stub's support of the
@samp{qXfer:osdata:read} packet, see @ref{qXfer osdata read}.

@table @code
@kindex info os
-@item info os
-List the types of OS information available for the target. If the
-target does not return a list of possible types, this command will
-report an error.
+@item info os INFOTYPE
+
+Display OS information of the requested type.
+
+On @sc{gnu}/Linux, the following values of INFOTYPE are valid:

+@anchor{linux info os infotypes}
+@table @code
@kindex info os processes
-@item info os processes
+@item processes
Display the list of processes on the target. For each process,
-@value{GDBN} prints the process identifier, the name of the user, and
-the command corresponding to the process.
+@value{GDBN} prints the process identifier, the name of the user, the
+command corresponding to the process, and the list of processor cores
+that the process is currently running on.
+
+@kindex info os procgroups
+@item procgroups
+Display the list of process groups on the target. For each process,
+@value{GDBN} prints the identifier of the process group that it belongs
+to, the command corresponding to the process group leader, the process
+identifier, and the command line of the process. The list is sorted
+first by the process group identifier, then by the process identifier,
+so that processes belonging to the same process group are grouped together
+and the process group leader is listed first.
+
+@kindex info os threads
+@item threads
+Display the list of threads running on the target. For each thread,
+@value{GDBN} prints the identifier of the process that the thread
+belongs to, the command of the process, the thread identifier, and the
+processor core that it is currently running on. The main thread of a
+process is not listed.
+
+@kindex info os files
+@item files
+Display the list of open file descriptors on the target. For each
+file descriptor, @value{GDBN} prints the identifier of the process
+owning the descriptor, the command of the owning process, the value
+of the descriptor, and the target of the descriptor.
+
+@kindex info os sockets
+@item sockets
+Display the list of Internet-domain sockets on the target. For each
+socket, @value{GDBN} prints the address and port of the local and
+remote endpoints, the current state of the connection, the creator of
+the socket, the IP address family of the socket, and the type of the
+connection.
+
+@kindex info os shm
+@item shm
+Display the list of all System V shared-memory regions on the target.
+For each shared-memory region, @value{GDBN} prints the region key,
+the shared-memory identifier, the access permissions, the size of the
+region, the process that created the region, the process that last
+attached to or detached from the region, the current number of live
+attaches to the region, and the times at which the region was last
+attached to, detach from, and changed.
+
+@kindex info os semaphores
+@item semaphores
+Display the list of all System V semaphore sets on the target. For each
+semaphore set, @value{GDBN} prints the semaphore set key, the semaphore
+set identifier, the access permissions, the number of semaphores in the
+set, the user and group of the owner and creator of the semaphore set,
+and the times at which the semaphore set was operated upon and changed.
+
+@kindex info os msg
+@item msg
+Display the list of all System V message queues on the target. For each
+message queue, @value{GDBN} prints the message queue key, the message
+queue identifier, the access permissions, the current number of bytes
+on the queue, the current number of messages on the queue, the processes
+that last sent and received a message on the queue, the user and group
+of the owner and creator of the message queue, the times at which a
+message was last sent and received on the queue, and the time at which
+the message queue was last changed.
+
+@kindex info os modules
+@item modules
+Display the list of all loaded kernel modules on the target. For each
+module, @value{GDBN} prints the module name, the size of the module in
+bytes, the number of times the module is used, the dependencies of the
+module, the status of the module, and the address of the loaded module
+in memory.
+@end table
+
+@item info os
+If INFOTYPE is omitted, then list the types of OS information available
+for the target. If the target does not return a list of possible types,
+this command will report an error.
@end table

@node Memory Region Attributes
Index: gdb/testsuite/gdb.base/info-os.c
===================================================================
RCS file: gdb/testsuite/gdb.base/info-os.c
diff -N gdb/testsuite/gdb.base/info-os.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/testsuite/gdb.base/info-os.c 12 Oct 2011 18:19:31 -0000
@@ -0,0 +1,115 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2011 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/msg.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void *
+thread_proc (void *args)
+{
+ pthread_mutex_lock (&mutex);
+ pthread_mutex_unlock (&mutex);
+}
+
+int
+main (void)
+{
+ const int flags = IPC_CREAT | 0666;
+ int shmid, semid, msqid;
+ FILE *fd;
+ pthread_t thread;
+ struct sockaddr_in sock_addr;
+ int sock;
+ unsigned short port;
+ socklen_t size;
+ int status;
+
+ if ((shmid = shmget (3925, 4096, flags | IPC_EXCL)) < 0)
+ {
+ /* Attempt to delete the existing shared-memory region, then
+ recreate it. */
+ shmctl (shmget (3925, 4096, flags), IPC_RMID, NULL);
+ if ((shmid = shmget (3925, 4096, flags | IPC_EXCL)) < 0)
+ {
+ printf ("Cannot create shared-memory region.\n");
+ return 1;
+ }
+ }
+
+ semid = semget (7428, 1, flags);
+ msqid = msgget (5294, flags);
+ fd = fopen ("/dev/null", "r");
+
+ /* Lock the mutex to prevent the new thread from finishing immediately. */
+ pthread_mutex_lock (&mutex);
+ pthread_create (&thread, NULL, thread_proc, 0);
+
+ sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sock < 0)
+ {
+ printf ("Cannot create socket.\n");
+ return 1;
+ }
+
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_port = 0; /* Bind to a free port. */
+ sock_addr.sin_addr.s_addr = htonl (INADDR_ANY);
+
+ status = bind (sock, (struct sockaddr *) &sock_addr, sizeof (sock_addr));
+ if (status < 0)
+ {
+ printf ("Cannot bind socket.\n");
+ return 1;
+ }
+
+ /* Find the assigned port number of the socket. */
+ size = sizeof (sock_addr);
+ status = getsockname (sock, (struct sockaddr *) &sock_addr, &size);
+ if (status < 0)
+ {
+ printf ("Cannot find name of socket.\n");
+ return 1;
+ }
+ port = ntohs (sock_addr.sin_port);
+
+ status = listen (sock, 1);
+ if (status < 0)
+ {
+ printf ("Cannot listen on socket.\n");
+ return 1;
+ }
+
+ /* Set breakpoint here. */
+
+ shmctl (shmid, IPC_RMID, NULL);
+ semctl (semid, 0, IPC_RMID, NULL);
+ msgctl (msqid, IPC_RMID, NULL);
+ fclose (fd);
+ close (sock);
+
+ pthread_mutex_unlock (&mutex);
+ pthread_join (thread, NULL);
+
+ return 0;
+}
Index: gdb/testsuite/gdb.base/info-os.exp
===================================================================
RCS file: gdb/testsuite/gdb.base/info-os.exp
diff -N gdb/testsuite/gdb.base/info-os.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/testsuite/gdb.base/info-os.exp 12 Oct 2011 18:19:31 -0000
@@ -0,0 +1,110 @@
+# Copyright 2011 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set testfile "info-os"
+set srcfile ${testfile}.c
+
+# This test is Linux-only.
+if ![istarget *-*-linux*] then {
+ unsupported "info-os.exp"
+ return -1
+}
+
+# Support for XML-output is needed to run this test.
+if [gdb_skip_xml_test] then {
+ unsupported "info-os.exp"
+ return -1
+}
+
+# Compile test program.
+if { [prepare_for_testing ${testfile}.exp $testfile $srcfile {debug additional_flags=-lpthread}] } {
+ fail "cannot compile test program"
+ return -1
+}
+
+if ![runto_main] then {
+ fail "cannot run to main"
+ return -1;
+}
+
+# Get PID of test program.
+set inferior_pid -1
+set test "get inferior process ID"
+gdb_test_multiple "call getpid()" $test {
+ -re ".* = ($decimal).*$gdb_prompt $" {
+ set inferior_pid $expect_out(1,string)
+ pass $test
+ }
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "Set breakpoint here"]
+gdb_continue_to_breakpoint "Set breakpoint here"
+
+# Get IDs of the IPC object instances.
+set shmid -1
+set test "get shared memory ID"
+gdb_test_multiple "print shmid" $test {
+ -re ".* = ($decimal).*$gdb_prompt $" {
+ set shmid $expect_out(1,string)
+ pass $test
+ }
+}
+
+set semid -1
+set test "get semaphore ID"
+gdb_test_multiple "print semid" $test {
+ -re ".* = ($decimal).*$gdb_prompt $" {
+ set semid $expect_out(1,string)
+ pass $test
+ }
+}
+
+set msqid -1
+set test "get message queue ID"
+gdb_test_multiple "print msqid" $test {
+ -re ".* = ($decimal).*$gdb_prompt $" {
+ set msqid $expect_out(1,string)
+ pass $test
+ }
+}
+
+# Get port number of test socket.
+set port -1
+set test "get socket port number"
+gdb_test_multiple "print port" $test {
+ -re ".* = ($decimal).*$gdb_prompt $" {
+ set port $expect_out(1,string)
+ pass $test
+ }
+}
+
+# Test output of the 'info os' commands against the expected results.
+gdb_test "info os processes" ".*pid +user +command +cores.*$inferior_pid +\\S+ +\\S*info-os +\[0-9\]+.*" "get process list"
+gdb_test "info os procgroups" ".*pgid +leader command +pid +command line.*$inferior_pid +info-os +$inferior_pid +\\S*info-os.*" "get process groups"
+gdb_test "info os threads" ".*pid +command +tid +core.*$inferior_pid +info-os +\\d+ +\\d+.*" "get threads"
+gdb_test "info os files" ".*pid +command +file descriptor +name.*$inferior_pid +info-os +\\d+ +/dev/null.*" "get file descriptors"
+gdb_test "info os sockets" ".*local address +local port +remote address +remote port +state +user +family +protocol.*0\\.0\\.0\\.0 +$port +0\\.0\\.0\\.0 +0 +LISTEN +\\S+ +INET +STREAM.*" "get internet-domain sockets"
+gdb_test "info os shm" ".*key +shmid +permissions +size +creator command +last op\\. command +num attached +user +group +creator user +creator group +last shmat\\(\\) time +last shmdt\\(\\) time +last shmctl\\(\\) time.*3925 +$shmid +666 +4096 +info-os +.*" "get shared-memory regions"
+gdb_test "info os semaphores" ".*key +semid +permissions +num semaphores +user +group +creator user +creator group +last semop\\(\\) time +last semctl\\(\\) time.*7428 +$semid +666 +1 +.*" "get semaphores"
+gdb_test "info os msg" ".*key +msqid +permissions +num used bytes +num messages +last msgsnd\\(\\) command +last msgrcv\\(\\) command +user +group +creator user +creator group +last msgsnd\\(\\) time +last msgrcv\\(\\) time +last msgctl\\(\\) time.*5294 +$msqid +666 +.*" "get message queues"
+
+# The SysV IPC primitives linger on after the creating process is killed
+# unless they are destroyed explicitly, so allow the test program to tidy
+# up after itself. Note that the test program attempts to delete and
+# recreate the shared-memory region if it already exists, in case a
+# previous run failed before having a chance to clean up. The tests for
+# semaphores and message queues should still work with primitives from
+# previous runs.
+send_gdb "continue\n"