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. */
process_map(const prmap_t * map,range_t ** ranges_head,range_t ** ranges_tail,size_t page_size)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. */
read_proc_map(size_t page_size)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
print_ranges(const range_t * head)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
sum_ranges(const range_t * head)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
map_segment(void * fixed_addr)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
main(int argc,const char * argv[])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