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.*
16ELF Header
17  ...
18  e_phoff: 0x34  e_phentsize: 32  e_phnum: PN_XNUM (see shdr[0].sh_info)
19                                  ^^^^^^^^^^^^^^^^
20Section 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. */
50typedef 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. */
58static 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. */
108static 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
141static 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
150static 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
162static 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
179int 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