DCMD_PROC_MAPINFO and DCMD_PROC_PAGEDATA

The next thing that we can do with a process is look at the memory segments that it has in use. There are two devctl() commands to accomplish this: DCMD_PROC_MAPINFO and DCMD_PROC_PAGEDATA (also described in the QNX Neutrino Programmer's Guide).

Both commands use the same data structure (edited for clarity):

typedef struct _procfs_map_info {
  uint64_t   vaddr;
  uint64_t   size;
  uint32_t   flags;
  dev_t      dev;
  off_t      offset;
  ino_t      ino;
} procfs_mapinfo;

The original data structure declaration has #ifdef's for 32 versus 64 bit sizes of the offset and ino members.

The procfs_mapinfo is used in its array form, meaning that we must allocate sufficient space for all of the memory segments that we will be getting information about. Practically speaking, I've managed just fine with 512 (MAX_SEGMENTS) elements. When I use this call in code, I compare the number of elements available (returned by the devctl() function) and ensure that it is less than the constant MAX_SEGMENTS. In the unlikely event that 512 elements are insufficient, you can allocate the array dynamically and reissue the devctl() call with a bigger buffer. In practice, the 10 to 100 element range is sufficient; 512 is overkill.

Here's how the call is used:

#define MAX_SEGMENTS  512

void
dump_procfs_map_info (int fd, int pid)
{
  procfs_mapinfo  membufs [MAX_SEGMENTS];
  int             nmembuf;
  int             i;
  int             sts;

  // fetch information about the memory regions for this pid
  sts = devctl (fd, DCMD_PROC_PAGEDATA, membufs, sizeof (membufs), &nmembuf);
  if (sts != EOK) {
    fprintf(stderr, "%s:  PAGEDATA process %d, error %d (%s)\n",
            progname, pid, sts, strerror (sts));
    exit (EXIT_FAILURE);
  }

  // check to see we haven't overflowed
  if (nmembuf > MAX_SEGMENTS) {
    fprintf (stderr, "%s: proc %d has > %d memsegs (%d)!!!\n",
             progname, pid, MAX_SEGMENTS, nmembuf);
    exit (EXIT_FAILURE);
  }

  for (i = 0; i < nmembuf; i++) {
    // now we can print/analyze the data
  }
}

Here's the output for the pipe process (I've added blank lines for clarity):

Info from DCMD_PROC_PAGEDATA
  Buff# --vaddr--- ---size--- ---flags-- ---dev---- ---ino----

  [  0] 0x07F22000 0x00001000 0x01001083 0x00000002 0x00000001
  [  1] 0x07F23000 0x0001F000 0x01001783 0x00000002 0x00000001
  [  2] 0x07F42000 0x00001000 0x01401783 0x00000002 0x00000001

  [  3] 0x07F43000 0x00001000 0x01001083 0x00000002 0x00000001
  [  4] 0x07F44000 0x0001F000 0x01001783 0x00000002 0x00000001
  [  5] 0x07F63000 0x00001000 0x01401783 0x00000002 0x00000001

  [  6] 0x07F64000 0x00001000 0x01001083 0x00000002 0x00000001
  [  7] 0x07F65000 0x0001F000 0x01001783 0x00000002 0x00000001
  [  8] 0x07F84000 0x00001000 0x01401783 0x00000002 0x00000001

  [  9] 0x07FA6000 0x00001000 0x01001083 0x00000002 0x00000001
  [ 10] 0x07FA7000 0x0001F000 0x01001783 0x00000002 0x00000001
  [ 11] 0x07FC6000 0x00001000 0x01401783 0x00000002 0x00000001

  [ 12] 0x07FC7000 0x00001000 0x01001083 0x00000002 0x00000001
  [ 13] 0x07FC8000 0x0007E000 0x01001383 0x00000002 0x00000001
  [ 14] 0x08046000 0x00002000 0x01401383 0x00000002 0x00000001

  [ 15] 0x08048000 0x00004000 0x00400571 0x00000001 0x00000009

  [ 16] 0x0804C000 0x00001000 0x01400372 0x00000001 0x00000009
  [ 17] 0x0804D000 0x00024000 0x01400303 0x00000002 0x00000001

  [ 18] 0xB0300000 0x0004E000 0x00410561 0x00000004 0xB0300000
  [ 19] 0xB034E000 0x00004000 0x01400772 0x00000004 0xB0300000

This tells us that there are 20 memory regions in use, and gives us the virtual address, the size, flags, device number, and inode for each one. Let's correlate this to the pidin output:

# pidin -p4105 mem
 pid tid name         prio STATE       code  data     stack
4105   1 sbin/pipe       10o RECEIVE       16K  148K  4096(132K)
4105   2 sbin/pipe       10o RECEIVE       16K  148K  4096(132K)
4105   4 sbin/pipe       10o RECEIVE       16K  148K  4096(132K)
4105   5 sbin/pipe       10o RECEIVE       16K  148K  4096(132K)
    ldqnx.so.2     @b0300000       312K   16K
Regions 0, 3, 6, 9 and 12
These are the guard pages at the end of the stacks, one for each of the five threads.
Regions 1, 4, 7, 10 and 13
These are the growth areas for the stacks, one for each of the five threads. This memory is physically allocated on demand; these regions serve to reserve the virtual address ranges. This corresponds to the "(132K)" from the pidin output.
Regions 2, 5, 8, 11 and 14
These are the in-use 4 KB stack segments, one for each of the five threads. Only four threads are alive—we'll discuss this below. This corresponds to the 4096 from the pidin output.
Region 15
This is the 16 KB of code for pipe.
Regions 16 and 17
These are the data areas (4 KB and 144 KB, for a total of 148 KB).
Regions 18 and 19
These are for the shared object, ldqnx.so.2. Region 18 is the code area, region 19 is the data area. These correspond to the ldqnx.so.2 line from the pidin output.

The key to decoding the regions is to look at the flags member. You'll notice that there are two commands: DCMD_PROC_PAGEDATA and DCMD_PROC_MAPINFO. Both of these are used to obtain information about memory regions. However, DCMD_PROC_MAPINFO merges non-PG_* regions together, whereas DCMD_PROC_PAGEDATA lists them individually. This also implies that the three PG_* flags (PG_HWMAPPED, PG_REFERENCED, and PG_MODIFIED are valid only with DCMD_PROC_PAGEDATA).

The flags member is a bitmap, broken down as follows (with each flag's value, defined in <sys/mman.h>, shown in parentheses):

0  Reserved
0  Reserved
x  MAP_CONSTRAINED  (0x02000000)
x  MAP_SYSRAM       (0x01000000)

0  Reserved
x  PG_HWMAPPED      (0x00400000)
x  PG_REFERENCED    (0x00200000)
x  PG_MODIFIED      (0x00100000)

x  MAP_ANON         (0x00080000)
x  MAP_BELOW16M     (0x00040000)
x  MAP_NOX64K       (0x00020000)
x  MAP_PHYS         (0x00010000)

0  Reserved
x  MAP_NOINIT       (0x00004000)
x  MAP_BELOW        (0x00002000)
x  MAP_STACK        (0x00001000)

x  PROT_NOCACHE     (0x00000800)
x  PROT_EXEC        (0x00000400)
x  PROT_WRITE       (0x00000200)
x  PROT_READ        (0x00000100)

x  MAP_LAZY         (0x00000080)
x  MAP_NOSYNCFILE   (0x00000040)
x  MAP_ELF          (0x00000020)
x  MAP_FIXED        (0x00000010)

0  Reserved
0  Reserved
x  See below.
x  See below.

The last two bits are used together to indicate these flags:

00 MAP_FILE         (0x00000000)
01 MAP_SHARED       (0x00000001)
10 MAP_PRIVATE      (0x00000002)
11 MAP_PRIVATEANON  (0x00000003)

By looking for a "tell-tale" flag, namely MAP_STACK (0x00001000), I was able to find all of the stack segments (regions 0 through 14). Having eliminated those, regions 15, 18, and 19 are marked as PROT_EXEC (0x00000400), so must be executable (the data area of the shared library is marked executable). By process of elimination, regions 16 and 17 aren't marked executable; therefore, they're data.