Wednesday, April 20, 2016

How to see memory layout of my program in C during run-time?

Leave a Comment

I would like to see Memory layout of my program in C so that i can understand all the different segments of the Memory practically during run-time like change in BSS or Heap for ex ?

3 Answers

Answers 1

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] = '\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]%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] = '\0';          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, "\n");         fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);         fprintf(stderr, "       %s PID\n", argv[0]);         fprintf(stderr, "\n");         fprintf(stderr, "You can use PID 0 as an alias for the command itself.\n");         fprintf(stderr, "\n");         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.\n", pid, strerror(errno));                 return EXIT_FAILURE;             }              printf("Process %d:\n", pid);             for (curr = list; curr != NULL; curr = curr->next)                 printf("\t%p .. %p: %s\n", curr->start, (void *)((char *)curr->start + curr->length), curr->name);             printf("\n");             fflush(stdout);              free_mem_stats(list);          } else {             fprintf(stderr, "%s: Invalid PID.\n", 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|^  *|\t|' -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).

Answers 2

Another alternative is pmap tool which dumps the process memory mapping details:

    pmap [ -x | -d ] [ -q ] pids...     pmap -V 

pmap is part of procps collection.

Also if you are interest in physical mapping, you can take a look at pagemap, which is made available in recent Linux Kernel to let process know it's physical memory info. It might be useful for user space driver development where user space process need to find physical address of a buffer as DMA destination.

https://www.kernel.org/doc/Documentation/vm/pagemap.txt

Answers 3

If you're on Linux use gcore to get a static core dump, it's part of gdb...

gcore $pid > Corefile 

or

gcore -o core_dump $pid 

To debug a running program attach to it using gdb

gdb -p 1234 

then poke around in it. To see how it's layed out

(gdb) maint info sections Exec file:     `/home/foo/program', file type elf32-i386.  [0]     0x8048134->0x8048147 at 0x00000134: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS  [1]     0x8048148->0x8048168 at 0x00000148: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS  [2]     0x8048168->0x804818c at 0x00000168: .note.gnu.build-id ALLOC LOAD   ..... ..... [23]     0x8049a40->0x8049ad1 at 0x00000a40: .data ALLOC LOAD DATA HAS_CONTENTS [24]     0x8049ad1->0x8049ad4 at 0x00000ad1: .bss ALLOC 

To poke around in registers use

(gdb) info all-registers eax            0xfffffdfc   -516 ecx            0x0  0 edx            0x1  1 ebx            0xffeedc28   -1123288 esp            0xffeedc0c   0xffeedc0c ebp            0xffeedc78   0xffeedc78 esi            0x1308   4872 edi            0x45cf   17871 .... snipped 

If you want to see the assembly used for a particular function use disassemble. It can also be used with addresses in memory.

(gdb) disassemble main Dump of assembler code for function main:    0x080483f0 <+0>: lea    0x4(%esp),%ecx    0x080483f4 <+4>: and    $0xfffffff0,%esp    0x080483f7 <+7>: mov    $0x8048780,%edx    0x080483fc <+12>:    pushl  -0x4(%ecx)    0x080483ff <+15>:    push   %ebp    0x08048400 <+16>:    mov    %esp,%ebp    ....    .... 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment