1 /* Tests that Valgrind coredump support works correctly even when
2    number of segments exceeds 0xffff.
3    For this to work, large number of pages is mmap()'ed into the
4    process (virtual) address space. These pages must not be adjacent
5    to each other otherwise the memory manager will coalesce them
6    into a single one. So they are one page apart.
7 
8    NOTE: Valgrind's internal limit VG_N_SEGMENTS must be at least
9    140000 otherwise you get a fatal error similar to this one:
10        "FATAL: VG_N_SEGMENTS is too low."
11 
12    Test case passes successfully if the number of segments is
13    correctly displayed in elfdump output:
14 
15    $ elfdump -e vgcore.*
16 ELF Header
17   ...
18   e_phoff: 0x34  e_phentsize: 32  e_phnum: PN_XNUM (see shdr[0].sh_info)
19                                   ^^^^^^^^^^^^^^^^
20 Section Header[0]:  (ELF Ehdr extensions)
21   ...
22     sh_link: 0 (e_shstrndx)  sh_info: 65554 (e_phnum)
23                              ^^^^^^^^^^^^^^^^^^^^^^^^
24 */
25 
26 #include <assert.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <sys/ipc.h>
35 #include <sys/mman.h>
36 #include <sys/procfs.h>
37 #include <sys/stat.h>
38 
39 #define SEGMENTS (0xffff + 2)
40 
41 #if 0
42 #define DEBUG(format, ...) printf(format, ## __VA_ARGS__)
43 #else
44 #define DEBUG(format, ...)
45 #endif
46 
47 #define PRINT(format, ...) printf(format, ## __VA_ARGS__)
48 
49 /* Represents a free range of a virtual address space. */
50 typedef struct range {
51    uintptr_t     start;
52    uintptr_t     end;
53    size_t        size;
54    struct range *next;
55 } range_t;
56 
57 /* Processes a single prmap_t entry and builds the free ranges. */
58 static int process_map(const prmap_t *map, range_t **ranges_head,
59                        range_t **ranges_tail, size_t page_size)
60 {
61    assert(map != NULL);
62    assert(ranges_head != NULL);
63    assert(ranges_tail != NULL);
64 
65    range_t *head = *ranges_head;
66    range_t *tail = *ranges_tail;
67 
68    DEBUG("processing map with addr=%p and size=%zu\n",
69          map->pr_vaddr, map->pr_size);
70 
71    if (head == NULL) {
72       head = calloc(1, sizeof(range_t));
73       if (head == NULL) {
74          fprintf(stderr, "calloc failed\n");
75          return -1;
76       }
77       head->start = (uintptr_t) page_size; // do not start at address '0'
78 
79       tail = head;
80       *ranges_head = head;
81       *ranges_tail = tail;
82    }
83 
84    if ((map->pr_vaddr < tail->start) ||
85        (map->pr_vaddr - tail->start < 3 * page_size)) {
86       DEBUG("last range at %p is too small, skipping it\n",
87             tail->start);
88       tail->start = map->pr_vaddr + map->pr_size + page_size;
89       return 0;
90    }
91 
92    tail->end = map->pr_vaddr - page_size;
93    tail->size = tail->end - tail->start;
94 
95    range_t *new_one = calloc(1, sizeof(range_t));
96    if (new_one == NULL) {
97       fprintf(stderr, "calloc failed\n");
98       return -1;
99    }
100 
101    new_one->start = map->pr_vaddr + map->pr_size + page_size;
102    tail->next = new_one;
103    *ranges_tail = new_one;
104    return 0;
105 }
106 
107 /* Reads /proc/self/map and builds free ranges. */
108 static range_t *read_proc_map(size_t page_size)
109 {
110    int fd = open("/proc/self/map", O_RDONLY);
111    if (fd == -1) {
112       int error = errno;
113       fprintf(stderr, "open failed: %s (%d)\n", strerror(error), error);
114       return NULL;
115    }
116 
117    prmap_t map;
118    range_t *ranges_head = NULL;
119    range_t *ranges_tail = NULL;
120 
121    ssize_t bytes = read(fd, &map, sizeof(map));
122    while (bytes == sizeof(map)) {
123       if (map.pr_size != 0) {
124          if (process_map(&map, &ranges_head, &ranges_tail,
125                          page_size) != 0) {
126             return NULL;
127          }
128       }
129       bytes = read(fd, &map, sizeof(map));
130    }
131 
132    if (ranges_tail != NULL) {
133       ranges_tail->end = (uintptr_t) ~0;
134       ranges_tail->size = ranges_tail->end - ranges_tail->start;
135    }
136 
137    close(fd);
138    return ranges_head;
139 }
140 
141 static void print_ranges(const range_t *head)
142 {
143    while (head != NULL) {
144       DEBUG("free range [%8p - %8p] of size %7zuK\n",
145             head->start, head->end, head->size / 1024);
146       head = head->next;
147    }
148 }
149 
150 static size_t sum_ranges(const range_t *head)
151 {
152    size_t sum = 0;
153 
154    while (head != NULL) {
155       sum += head->size;
156       head = head->next;
157    }
158 
159    return sum;
160 }
161 
162 static void *map_segment(void *fixed_addr)
163 {
164    int flags = MAP_NORESERVE | MAP_ANON | MAP_PRIVATE | MAP_FIXED;
165    void *addr = mmap(fixed_addr, 1, PROT_READ | PROT_WRITE,
166                      flags, -1, 0);
167    if (addr == MAP_FAILED) {
168       int error = errno;
169       fprintf(stderr, "mmap failed: %s (%d)\n", strerror(error), error);
170       return NULL;
171    }
172    assert(addr == fixed_addr);
173 
174    *((char *) addr) = 1; // make the mmap'ed page dirty
175    // DEBUG("mmap(%8p) = %8p\n", fixed_addr, addr);
176    return addr;
177 }
178 
179 int main(int argc, const char *argv[])
180 {
181    long page_size = sysconf(_SC_PAGESIZE);
182    if (page_size == -1) {
183       perror("sysconf");
184       return 1;
185    }
186 
187    PRINT("Page size determined as %ld bytes.\n", page_size);
188 
189    range_t *ranges = read_proc_map(page_size);
190    print_ranges(ranges);
191 
192    size_t sum = sum_ranges(ranges);
193    if (sum < SEGMENTS * page_size) {
194       fprintf(stderr, "Free (virtual) address space cannot accomodate "
195               "%u pages.\n", SEGMENTS);
196       return 1;
197    }
198 
199    PRINT("mmap()'ing %u segments:", SEGMENTS);
200    fflush(stdout);
201 
202    unsigned int segment = 0;
203    while ((ranges != NULL) && (segment < SEGMENTS)) {
204       unsigned int page;
205       for (page = 0; page < ranges->size / (2 * page_size); page++) {
206          uintptr_t start = ranges->start + 2 * page * page_size;
207          void *addr = map_segment((void *) start);
208          if (addr == NULL) {
209             fprintf(stderr, "Mapping failed for segment %u at address "
210                     "%" PRIxPTR ".\n", segment, start);
211             return 1;
212          }
213 
214          segment += 1;
215          if (segment >= SEGMENTS) {
216             break;
217          }
218 
219          if (segment % (SEGMENTS / 10) == 0) {
220             PRINT(" %u0%%", segment / (SEGMENTS / 10));
221             fflush(stdout);
222          }
223       }
224       ranges = ranges->next;
225    }
226    assert(segment == SEGMENTS);
227 
228    PRINT(".\nDumping core...\n");
229    char *nihil = NULL;
230    *nihil = 0; // SEGV here
231    fprintf(stderr, "Should not reach here.\n");
232 
233    return 0;
234 }
235