In Linux, for process PID, look at /proc/PID/maps
and /proc/PID/smaps
pseudofiles. (The process itself can use /proc/self/maps
and /proc/self/smaps
.)
Their contents are documented in man 5 proc.
Here's an example of how you might read the contents into a linked list of address range structures.
mem-stats.h:
#ifndef MEM_STATS_H
#define MEM_STATS_H
#include <stdlib.h>
#include <sys/types.h>
#define PERMS_READ 1U
#define PERMS_WRITE 2U
#define PERMS_EXEC 4U
#define PERMS_SHARED 8U
#define PERMS_PRIVATE 16U
typedef struct address_range address_range;
struct address_range {
struct address_range *next;
void *start;
size_t length;
unsigned long offset;
dev_t device;
ino_t inode;
unsigned char perms;
char name[];
};
address_range *mem_stats(pid_t);
void free_mem_stats(address_range *);
#endif /* MEM_STATS_H */
mem-stats.c:
#define _POSIX_C_SOURCE 200809L
#define _BSD_SOURCE
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "mem-stats.h"
void free_mem_stats(address_range *list)
{
while (list) {
address_range *curr = list;
list = list->next;
curr->next = NULL;
curr->length = 0;
curr->perms = 0U;
curr->name[0] = '';
free(curr);
}
}
address_range *mem_stats(pid_t pid)
{
address_range *list = NULL;
char *line = NULL;
size_t size = 0;
FILE *maps;
if (pid > 0) {
char namebuf[128];
int namelen;
namelen = snprintf(namebuf, sizeof namebuf, "/proc/%ld/maps", (long)pid);
if (namelen < 12) {
errno = EINVAL;
return NULL;
}
maps = fopen(namebuf, "r");
} else
maps = fopen("/proc/self/maps", "r");
if (!maps)
return NULL;
while (getline(&line, &size, maps) > 0) {
address_range *curr;
char perms[8];
unsigned int devmajor, devminor;
unsigned long addr_start, addr_end, offset, inode;
int name_start = 0;
int name_end = 0;
if (sscanf(line, "%lx-%lx %7s %lx %u:%u %lu %n%*[^
]%n",
&addr_start, &addr_end, perms, &offset,
&devmajor, &devminor, &inode,
&name_start, &name_end) < 7) {
fclose(maps);
free(line);
free_mem_stats(list);
errno = EIO;
return NULL;
}
if (name_end <= name_start)
name_start = name_end = 0;
curr = malloc(sizeof (address_range) + (size_t)(name_end - name_start) + 1);
if (!curr) {
fclose(maps);
free(line);
free_mem_stats(list);
errno = ENOMEM;
return NULL;
}
if (name_end > name_start)
memcpy(curr->name, line + name_start, name_end - name_start);
curr->name[name_end - name_start] = '';
curr->start = (void *)addr_start;
curr->length = addr_end - addr_start;
curr->offset = offset;
curr->device = makedev(devmajor, devminor);
curr->inode = (ino_t)inode;
curr->perms = 0U;
if (strchr(perms, 'r'))
curr->perms |= PERMS_READ;
if (strchr(perms, 'w'))
curr->perms |= PERMS_WRITE;
if (strchr(perms, 'x'))
curr->perms |= PERMS_EXEC;
if (strchr(perms, 's'))
curr->perms |= PERMS_SHARED;
if (strchr(perms, 'p'))
curr->perms |= PERMS_PRIVATE;
curr->next = list;
list = curr;
}
free(line);
if (!feof(maps) || ferror(maps)) {
fclose(maps);
free_mem_stats(list);
errno = EIO;
return NULL;
}
if (fclose(maps)) {
free_mem_stats(list);
errno = EIO;
return NULL;
}
errno = 0;
return list;
}
An example program to use the above, example.c:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "mem-stats.h"
int main(int argc, char *argv[])
{
int arg, pid;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "
");
fprintf(stderr, "Usage: %s [ -h | --help ]
", argv[0]);
fprintf(stderr, " %s PID
", argv[0]);
fprintf(stderr, "
");
fprintf(stderr, "You can use PID 0 as an alias for the command itself.
");
fprintf(stderr, "
");
return EXIT_SUCCESS;
}
for (arg = 1; arg < argc; arg++)
if (sscanf(argv[arg], " %i %c", &pid, &dummy) == 1) {
address_range *list, *curr;
if (!pid)
pid = getpid();
list = mem_stats((pid_t)pid);
if (!list) {
fprintf(stderr, "Cannot obtain memory usage of process %d: %s.
", pid, strerror(errno));
return EXIT_FAILURE;
}
printf("Process %d:
", pid);
for (curr = list; curr != NULL; curr = curr->next)
printf("%p .. %p: %s
", curr->start, (void *)((char *)curr->start + curr->length), curr->name);
printf("
");
fflush(stdout);
free_mem_stats(list);
} else {
fprintf(stderr, "%s: Invalid PID.
", argv[arg]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
and a Makefile to make building it, simple:
CC := gcc
CFLAGS := -Wall -Wextra -O2 -fomit-frame-pointer
LDFLAGS :=
PROGS := example
.PHONY: all clean
all: clean $(PROGS)
clean:
rm -f *.o $(PROGS)
%.o: %.c
$(CC) $(CFLAGS) -c $^
example: mem-stats.o example.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
Note that the three indented lines in the Makefile above must use tab characters, not spaces. It seems that the editor here converts tabs to spaces, so you need to fix that, for example by using
sed -e 's|^ *||' -i Makefile
If you don't fix the indentation, and use spaces in a Makefile, you'll see an error message similar to *** missing separator. Stop
.
Some editors automatically convert a tab keypress into a number of spaces, so you may need to delve into the editor settings of whatever editor you use. Often, editors keep a pasted tab character intact, so you can always try pasting a tab from another program.
To compile and run, save the above files and run:
make
./example 0
to print the memory ranges used by the example program itself. If you want to see, say, the memory ranges used by your PulseAudio daemon, run:
./example $(ps -o pid= -C pulseaudio)
Note that standard access restrictions apply. A normal user can only see the memory ranges of the processes that run as that user; otherwise you need superuser privileges (sudo
or similar).