#include "libunwind_i.h"
#ifdef UNW_REMOTE_ONLY
bool
unw_address_is_valid(UNUSED unw_word_t addr, UNUSED size_t len)
{
Debug(1, "remote-only invoked\n");
return false;
}
#else
#include <stdatomic.h>
static atomic_flag _unw_address_validator_initialized = ATOMIC_FLAG_INIT;
static int _mem_validate_pipe[2] = {-1, -1};
#ifdef HAVE_PIPE2
static int
_do_pipe2 (int pipefd[2])
{
return pipe2 (pipefd, O_CLOEXEC | O_NONBLOCK);
}
#else
static void
_set_pipe_flags (int fd)
{
int fd_flags = fcntl (fd, F_GETFD, 0);
int status_flags = fcntl (fd, F_GETFL, 0);
fd_flags |= FD_CLOEXEC;
fcntl (fd, F_SETFD, fd_flags);
status_flags |= O_NONBLOCK;
fcntl (fd, F_SETFL, status_flags);
}
static int
_do_pipe2 (int pipefd[2])
{
if (pipe (pipefd) < 0)
{
return -1;
}
_set_pipe_flags(pipefd[0]);
_set_pipe_flags(pipefd[1]);
return 0;
}
#endif
static int
_open_pipe (void)
{
if (_mem_validate_pipe[0] != -1)
close (_mem_validate_pipe[0]);
if (_mem_validate_pipe[1] != -1)
close (_mem_validate_pipe[1]);
return _do_pipe2 (_mem_validate_pipe);
}
static bool
_write_validate (unw_word_t addr)
{
int ret = -1;
ssize_t bytes = 0;
if (unlikely (!atomic_flag_test_and_set(&_unw_address_validator_initialized)))
{
if (_open_pipe () != 0)
{
return false;
}
}
do
{
char buf;
bytes = read (_mem_validate_pipe[0], &buf, 1);
}
while ( errno == EINTR );
if (!(bytes > 0 || errno == EAGAIN || errno == EWOULDBLOCK))
{
if (_open_pipe () != 0)
{
return false;
}
}
do
{
#ifdef HAVE_SYS_SYSCALL_H
ret = syscall (SYS_write, _mem_validate_pipe[1], addr, 1);
#else
ret = write (_mem_validate_pipe[1], (void *)addr, 1);
#endif
}
while ( errno == EINTR );
return ret > 0;
}
enum { NLGA = 4 };
#if defined(HAVE___CACHE_PER_THREAD) && HAVE___CACHE_PER_THREAD
static _Thread_local unw_word_t last_good_addr[NLGA];
static _Thread_local int lga_victim;
static bool
_is_cached_valid_mem(unw_word_t page_addr)
{
int i;
for (i = 0; i < NLGA; i++)
{
if (page_addr == last_good_addr[i])
return true;
}
return false;
}
static void
_cache_valid_mem(unw_word_t page_addr)
{
int i, victim;
victim = lga_victim;
for (i = 0; i < NLGA; i++)
{
if (last_good_addr[victim] == 0)
{
last_good_addr[victim] = page_addr;
return;
}
victim = (victim + 1) % NLGA;
}
last_good_addr[victim] = page_addr;
victim = (victim + 1) % NLGA;
lga_victim = victim;
}
#else
static _Atomic unw_word_t last_good_addr[NLGA];
static _Atomic int lga_victim;
static bool
_is_cached_valid_mem(unw_word_t page_addr)
{
int i;
for (i = 0; i < NLGA; i++)
{
if (page_addr == atomic_load(&last_good_addr[i]))
return true;
}
return false;
}
static void
_cache_valid_mem(unw_word_t page_addr)
{
unw_word_t zero = 0;
int victim = atomic_load(&lga_victim);
for (int i = 0; i < NLGA; i++)
{
if (atomic_compare_exchange_strong(&last_good_addr[victim], &zero, page_addr))
{
return;
}
victim = (victim + 1) % NLGA;
}
atomic_store(&last_good_addr[victim], page_addr);
victim = (victim + 1) % NLGA;
atomic_store(&lga_victim, victim);
}
#endif
bool
unw_address_is_valid(unw_word_t addr, size_t len)
{
if (len == 0)
return true;
unw_word_t start_page_addr = unw_page_start (addr);
if (start_page_addr == 0)
return false;
if (addr > (UNW_WORD_MAX - len - unw_page_size))
return false;
unw_word_t end_page_addr = unw_page_start (addr + (len - 1)) + unw_page_size;
for (unw_word_t page_addr = start_page_addr;
page_addr < end_page_addr;
page_addr += unw_page_size)
{
if (!_is_cached_valid_mem(page_addr))
{
if (!_write_validate (page_addr))
{
Debug(1, "returning false\n");
return false;
}
_cache_valid_mem(page_addr);
}
}
return true;
}
#endif