Я хотел бы увидеть макет памяти моей программы на C, чтобы я мог понимать все разные сегменты памяти практически во время выполнения, например, изменение BSS или Heap для ex?
Как увидеть макет памяти моей программы на C во время выполнения?
Ответ 1
В Linux для PID процесса посмотрите /proc/PID/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 [email protected]
Обратите внимание, что три строки с отступом в Makefile выше должны использовать символы табуляции, а не пробелы. Кажется, что редактор здесь преобразует вкладки в пробелы, поэтому вам нужно это исправить, например, используя
sed -e 's|^ *|\t|' -i Makefile
Если вы не исправите отступ и не используете пробелы в Makefile, вы увидите сообщение об ошибке, подобное *** missing separator. Stop
*** missing separator. Stop
Некоторые редакторы автоматически преобразуют нажатие клавиши tab в несколько пробелов, поэтому вам может потребоваться углубиться в настройки редактора любого используемого вами редактора. Часто редакторы сохраняют вставленный символ табуляции без изменений, поэтому вы всегда можете попробовать вставить вкладку из другой программы.
Для компиляции и запуска сохраните вышеуказанные файлы и запустите:
make
./example 0
распечатать диапазоны памяти, используемые самой программой-примером. Если вы хотите увидеть, скажем, диапазоны памяти, используемые вашим демоном PulseAudio, запустите:
./example $(ps -o pid= -C pulseaudio)
Обратите внимание, что применяются стандартные ограничения доступа. Обычный пользователь может видеть только области памяти процессов, которые выполняются от имени этого пользователя; в противном случае вам нужны привилегии суперпользователя (sudo
или аналогичные).
Ответ 2
Если вы используете 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
....
....
Ответ 3
Другой альтернативой является инструмент pmap, который сбрасывает детали отображения памяти процесса:
pmap [ -x | -d ] [ -q ] pids...
pmap -V
pmap является частью коллекции procps.
Также, если вы заинтересованы в физическом сопоставлении, вы можете взглянуть на pagemap, которая доступна в последнем ядре Linux, чтобы позволить процессу узнать информацию о физической памяти. Это может быть полезно для разработки драйверов пространства пользователя, где процесс пользовательского пространства должен найти физический адрес буфера в качестве назначения DMA.