Reputation: 6377
I'd like to dump the [vvar]
segment from a linux userspace program (which may be running on x86, arm, or mips...). My problem is that only the first page(s) of the vvar segment are mapped, and I get a sigbus when I attempt to access the unpopulated pages.
I had two workarounds, neither of which worked. The first was to install a sigbus handler, but that doesn't work, simply because I cannot mmap over the memory that is assigned to [vvar]
. Thus, when the sig-handler returns, the page will still not be mapped, and when it tries to rerun the same instruction, it will recursively generate another sigbus.
My second attempt was to try to determine which pages are mapped, and to only dump the populated pages. But unfortunately, the /proc/self/pagemap
does not seem to work for the [vvar]
pages, so I'm not sure how to tell if they're populated or not.
Is there a way to determine which vvar pages are mapped, or to recover from a sigbus when reading the vvar page?
The following is a simplified version of my code so far:
// read segment info from /proc/self/maps:
get_local_segment_info("[vvar]", &e);
// returns e.start_addr=0x7fffb4b6e000, e.size=16384
ASSERT(e.start_addr != NULL);
#if 1
gzwrite(gz_fd, e.start_addr, e.size);
// this fails with a sigbus
#else
self_pagemap_fd = open("/proc/self/pagemap",O_RDONLY);
ASSERT(self_pagemap_fd >= 0);
off64_t off = lseek64(self_pagemap_fd ,
(((uintptr_t)e.start_addr) / page_size) * sizeof(uint64_t),
SEEK_SET);
ASSERTf(off >= 0, "ERROR: could not lseek64 (%s)",strerror(errno));
size_t nread = read( self_pagemap_fd ,
(uint8_t *)pagemaps,
sizeof(uint64_t) * num_pages);
ASSERT(nread > 0);
printf("pagemap[0]=0x%" PRIx64 "\n", pagemaps[0]);
// prints pagemap[0]=0x0
...
// presumably, I'd walk through the pages here, and only copy the populated ones, but
// pagemaps contains all zeros, so I can't tell which pages are populated
#endif
Upvotes: 1
Views: 139
Reputation: 6377
I managed to solve this, and posting here in case anyone else runs into this issue.
This is possible by creating a SIGBUS handler that returns to a different spot. This can be done through siglongjmp
/sigsetjmp
. Basically it looks something like:
bool is_addr_mapped(const void *addr)
{
if(sigsetjmp(g_sigjmp_mark,1) == 0) {
g_do_siglongjmp = 1;
volatile char c = *(const char *)addr;
c=c; // avoid unused variable warning
g_do_siglongjmp = 0;
return TRUE;
} else {
g_do_siglongjmp = 0;
return FALSE;
}
}
And then, in the signal handler
static void
sig_handler(int signo, siginfo_t *si, void *uc)
{
if (signo == SIGBUS && g_do_siglongjmp)
{
siglongjmp(g_ecd_gzwrite_siginfo.sigjmp_mark, 1);
}
...
}
From there, you can simply loop through the pages, and copy the ones that are mapped, and skip those that are not.
Upvotes: 1