我希望看到用C语言编写的程序的内存布局,以便我可以在运行时实际理解内存的所有不同部分,例如BSS或Heap for ex?中的更改。
在Linux中,对于进程PID,请查看/proc/PID/maps和/proc/PID/smaps伪文件。(该过程本身可以使用/proc/self/maps和/proc/self/smaps。)
/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
某些编辑器会自动将tab按键转换为多个空格,因此您可能需要研究使用的任何编辑器的编辑器设置。通常,编辑者会完整保留粘贴的制表符,因此您始终可以尝试从另一个程序粘贴制表符。
tab
要编译并运行,请保存以上文件并运行:
make ./example 0
打印示例程序本身使用的内存范围。例如,如果您想查看PulseAudio守护程序使用的内存范围,请运行:
./example $(ps -o pid= -C pulseaudio)
请注意,适用标准访问限制。普通用户只能看到以该用户身份运行的进程的内存范围。否则,您需要超级用户权限(sudo或类似权限)。
sudo