Reputation: 6522
I can get the address of the end of the heap with sbrk(0)
, but is there any way to programmatically get the address of the start of the heap, other than by parsing the contents of /proc/self/maps
?
Upvotes: 17
Views: 12037
Reputation: 88
You may call sbrk(0)
to get the start of the heap, but you have to make sure no memory has been allocated yet.
The best way to do this is to assign the return value at the very beginning of main()
. Note that many functions do allocate memory under the hood, so a call to sbrk(0)
after a printf
, a memory utility like mtrace
or even a call to putenv
will already return an offset value.
Although much of what we can find say that the heap is right next to .bss, I am not sure what is in the difference between end
and the first break. Reading there seems to results in a segmentation fault.
The difference between the first break and the first address returned by malloc
is, among (probably) other thing:
malloc
ed block incuding:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
void print_heap_line();
int main(int argc, char const *argv[])
{
char* startbreak = sbrk(0);
printf("pid: %d\n", getpid()); // printf is allocating memory
char* lastbreak = sbrk(0);
printf("heap: [%p - %p]\n", startbreak, lastbreak);
long pagesize = sysconf(_SC_PAGESIZE);
long diff = lastbreak - startbreak;
printf("diff: %ld (%ld pages of %ld bytes)\n", diff, diff/pagesize, pagesize);
print_heap_line();
printf("\n\npress a key to finish...");
getchar(); // gives you a chance to inspect /proc/pid/maps yourself
return 0;
}
void print_heap_line() {
int mapsfd = open("/proc/self/maps", O_RDONLY);
if(mapsfd == -1) {
fprintf(stderr, "open() failed: %s.\n", strerror(errno));
exit(1);
}
char maps[BUFSIZ] = "";
if(read(mapsfd, maps, BUFSIZ) == -1){
fprintf(stderr, "read() failed: %s.\n", strerror(errno));
exit(1);
}
if(close(mapsfd) == -1){
fprintf(stderr, "close() failed: %s.\n", strerror(errno));
exit(1);
}
char* line = strtok(maps, "\n");
while((line = strtok(NULL, "\n")) != NULL) {
if(strstr(line, "heap") != NULL) {
printf("\n\nfrom /proc/self/maps:\n%s\n", line);
return;
}
}
}
pid: 29825
heap: [0x55fe05739000 - 0x55fe0575a000]
diff: 135168 (33 pages of 4096 bytes)
from /proc/self/maps:
55fe05739000-55fe0575a000 rw-p 00000000 00:00 0 [heap]
press a key to finish...
Upvotes: 2
Reputation: 17420
I think parsing /proc/self/maps
is the only reliable way on the Linux to find the heap segment. And do not forget that some allocators (including one in my SLES) do use for large blocks mmap()
thus the memory isn't part of the heap anymore and can be at any random location.
Otherwise, normally ld
adds a symbol which marks the end of all segments in elf and the symbol is called _end
. E.g.:
extern void *_end;
printf( "%p\n", &_end );
It matches the end of the .bss
, traditionally the last segment of elf. After the address, with some alignment, normally follows the heap. Stack(s) and mmap()s (including the shared libraries) are at the higher addresses of the address space.
I'm not sure how portable it is, but apparently it works same way on the Solaris 10. On HP-UX 11 the map looks different and heap appears to be merged with data segment, but allocations do happen after the _end
. On AIX, procmap
doesn't show heap/data segment at all, but allocations too get the addresses past the _end
symbol. So it seems to be at the moment quite portable.
Though, all considered, I'm not sure how useful that is.
P.S. The test program:
#include <stdio.h>
#include <stdlib.h>
char *ppp1 = "hello world";
char ppp0[] = "hello world";
extern void *_end; /* any type would do, only its address is important */
int main()
{
void *p = calloc(10000,1);
printf( "end:%p heap:%p rodata:%p data:%p\n", &_end, p, ppp1, ppp0 );
sleep(10000); /* sleep to give chance to look at the process memory map */
return 0;
}
Upvotes: 16