如何在运行时在C中查看我的程序的内存布局?

我想在C中看到我的程序的内存布局,这样我就可以在运行期间实际上理解内存中所有不同的片段,比如BSS或堆中的改变。

在Linux中,对于进程PID,请查看/proc/PID/maps/proc/PID/smaps伪文件。 (进程本身可以使用/proc/self/maps/proc/self/smaps 。)

他们的内容记录在man 5 proc中 。


下面是一个如何将内容读入地址范围结构链表的例子。

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; } 

一个使用上面的例子程序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; } 

和一个Makefile来简化构建:

 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 $@ 

请注意,上面的Makefile中的三个缩进行必须使用制表符,而不是空格。 看来这里的编辑器将标签转换为空格,所以你需要修复,例如通过使用

 sed -e 's|^ *|\t|' -i Makefile 

如果您不修复缩进,并在Makefile中使用空格,则会看到类似于*** missing separator. Stop的错误消息*** missing separator. Stop *** missing separator. Stop

有些编辑器会自动将制表符按键转换为多个空格,所以您可能需要深入研究您使用的任何编辑器的编辑器设置。 通常,编辑器会保持粘贴的制表符字符不变,所以您可以尝试粘贴另一个程序的制表符。

要编译并运行,保存上面的文件并运行

 make ./example 0 

打印示例程序本身使用的内存范围。 如果您想查看PulseAudio守护程序使用的内存范围,请运行

 ./example $(ps -o pid= -C pulseaudio) 

请注意,标准访问限制适用。 普通用户只能看到以该用户身份运行的进程的内存范围; 否则你需要超级用户权限( sudo或类似的)。

另一种替代方法是转储进程内存映射细节的pmap工具:

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

pmap是procps集合的一部分。

另外,如果您对物理映射感兴趣,则可以查看最近Linux内核中提供的页面映射,让流程知道它是物理内存信息。 对于用户空间驱动程序开发来说,用户空间进程需要找到缓冲区的物理地址作为DMA目标,这可能是有用的。

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

如果你在Linux上使用gcore来获得一个静态核心转储,它是gdb的一部分…

 gcore $pid > Corefile 

要么

 gcore -o core_dump $pid 

使用gdb调试正在运行的程序

 gdb -p 1234 

然后在它周围徘徊。 看看它是如何摆放的

 (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 

在寄存器中使用

 (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 

如果您想查看用于特定功能的组件,请使用disassemble 。 它也可以用于内存中的地址。

 (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 .... ....