152 lines
3.3 KiB
C
152 lines
3.3 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <signal.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <longjmp.h>
|
||
|
|
||
|
#ifdef __i386__
|
||
|
|
||
|
static jmp_buf buf;
|
||
|
|
||
|
static void segfault(int sig)
|
||
|
{
|
||
|
longjmp(buf, 1);
|
||
|
}
|
||
|
|
||
|
static int page_ok(unsigned long page)
|
||
|
{
|
||
|
unsigned long *address = (unsigned long *) (page << UM_KERN_PAGE_SHIFT);
|
||
|
unsigned long n = ~0UL;
|
||
|
void *mapped = NULL;
|
||
|
int ok = 0;
|
||
|
|
||
|
/*
|
||
|
* First see if the page is readable. If it is, it may still
|
||
|
* be a VDSO, so we go on to see if it's writable. If not
|
||
|
* then try mapping memory there. If that fails, then we're
|
||
|
* still in the kernel area. As a sanity check, we'll fail if
|
||
|
* the mmap succeeds, but gives us an address different from
|
||
|
* what we wanted.
|
||
|
*/
|
||
|
if (setjmp(buf) == 0)
|
||
|
n = *address;
|
||
|
else {
|
||
|
mapped = mmap(address, UM_KERN_PAGE_SIZE,
|
||
|
PROT_READ | PROT_WRITE,
|
||
|
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||
|
if (mapped == MAP_FAILED)
|
||
|
return 0;
|
||
|
if (mapped != address)
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now, is it writeable? If so, then we're in user address
|
||
|
* space. If not, then try mprotecting it and try the write
|
||
|
* again.
|
||
|
*/
|
||
|
if (setjmp(buf) == 0) {
|
||
|
*address = n;
|
||
|
ok = 1;
|
||
|
goto out;
|
||
|
} else if (mprotect(address, UM_KERN_PAGE_SIZE,
|
||
|
PROT_READ | PROT_WRITE) != 0)
|
||
|
goto out;
|
||
|
|
||
|
if (setjmp(buf) == 0) {
|
||
|
*address = n;
|
||
|
ok = 1;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (mapped != NULL)
|
||
|
munmap(mapped, UM_KERN_PAGE_SIZE);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
unsigned long os_get_top_address(void)
|
||
|
{
|
||
|
struct sigaction sa, old;
|
||
|
unsigned long bottom = 0;
|
||
|
/*
|
||
|
* A 32-bit UML on a 64-bit host gets confused about the VDSO at
|
||
|
* 0xffffe000. It is mapped, is readable, can be reprotected writeable
|
||
|
* and written. However, exec discovers later that it can't be
|
||
|
* unmapped. So, just set the highest address to be checked to just
|
||
|
* below it. This might waste some address space on 4G/4G 32-bit
|
||
|
* hosts, but shouldn't hurt otherwise.
|
||
|
*/
|
||
|
unsigned long top = 0xffffd000 >> UM_KERN_PAGE_SHIFT;
|
||
|
unsigned long test, original;
|
||
|
|
||
|
printf("Locating the bottom of the address space ... ");
|
||
|
fflush(stdout);
|
||
|
|
||
|
/*
|
||
|
* We're going to be longjmping out of the signal handler, so
|
||
|
* SA_DEFER needs to be set.
|
||
|
*/
|
||
|
sa.sa_handler = segfault;
|
||
|
sigemptyset(&sa.sa_mask);
|
||
|
sa.sa_flags = SA_NODEFER;
|
||
|
if (sigaction(SIGSEGV, &sa, &old)) {
|
||
|
perror("os_get_top_address");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
/* Manually scan the address space, bottom-up, until we find
|
||
|
* the first valid page (or run out of them).
|
||
|
*/
|
||
|
for (bottom = 0; bottom < top; bottom++) {
|
||
|
if (page_ok(bottom))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If we've got this far, we ran out of pages. */
|
||
|
if (bottom == top) {
|
||
|
fprintf(stderr, "Unable to determine bottom of address "
|
||
|
"space.\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
printf("0x%lx\n", bottom << UM_KERN_PAGE_SHIFT);
|
||
|
printf("Locating the top of the address space ... ");
|
||
|
fflush(stdout);
|
||
|
|
||
|
original = bottom;
|
||
|
|
||
|
/* This could happen with a 4G/4G split */
|
||
|
if (page_ok(top))
|
||
|
goto out;
|
||
|
|
||
|
do {
|
||
|
test = bottom + (top - bottom) / 2;
|
||
|
if (page_ok(test))
|
||
|
bottom = test;
|
||
|
else
|
||
|
top = test;
|
||
|
} while (top - bottom > 1);
|
||
|
|
||
|
out:
|
||
|
/* Restore the old SIGSEGV handling */
|
||
|
if (sigaction(SIGSEGV, &old, NULL)) {
|
||
|
perror("os_get_top_address");
|
||
|
exit(1);
|
||
|
}
|
||
|
top <<= UM_KERN_PAGE_SHIFT;
|
||
|
printf("0x%lx\n", top);
|
||
|
|
||
|
return top;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
unsigned long os_get_top_address(void)
|
||
|
{
|
||
|
/* The old value of CONFIG_TOP_ADDR */
|
||
|
return 0x7fc0002000;
|
||
|
}
|
||
|
|
||
|
#endif
|