summaryrefslogtreecommitdiff
path: root/src/aarch64
diff options
context:
space:
mode:
authorMikhail Durnev <mikhail_durnev@mentor.com>2021-03-05 21:41:31 +1000
committerDave Watson <dade.watson@gmail.com>2021-03-15 10:44:47 -0700
commitf8327f50be0413357816c2a41348455af476b9e5 (patch)
tree128e6ade256342c94fae70af0f3f6dc364458da8 /src/aarch64
parent7b498c21029effd85dc1d92cc93ada1d6c373dea (diff)
downloadlibunwind-f8327f50be0413357816c2a41348455af476b9e5.tar.gz
Port memory address checks from x86/x86_64 to Aarch64
Signed-off-by: Mikhail Durnev <mikhail_durnev@mentor.com>
Diffstat (limited to 'src/aarch64')
-rw-r--r--src/aarch64/Gglobal.c2
-rw-r--r--src/aarch64/Ginit.c248
-rw-r--r--src/aarch64/Ginit_local.c4
-rw-r--r--src/aarch64/Ginit_remote.c11
-rw-r--r--src/aarch64/Gresume.c2
-rw-r--r--src/aarch64/Gstep.c20
6 files changed, 279 insertions, 8 deletions
diff --git a/src/aarch64/Gglobal.c b/src/aarch64/Gglobal.c
index 854b5491..2987f2af 100644
--- a/src/aarch64/Gglobal.c
+++ b/src/aarch64/Gglobal.c
@@ -47,6 +47,8 @@ tdep_init (void)
dwarf_init ();
+ tdep_init_mem_validate ();
+
#ifndef UNW_REMOTE_ONLY
aarch64_local_addr_space_init ();
#endif
diff --git a/src/aarch64/Ginit.c b/src/aarch64/Ginit.c
index a3b933b6..2b08feb3 100644
--- a/src/aarch64/Ginit.c
+++ b/src/aarch64/Ginit.c
@@ -24,8 +24,11 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+#include <errno.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
+#include <stdatomic.h>
#include "unwind_i.h"
@@ -81,17 +84,256 @@ get_dyn_info_list_addr (unw_addr_space_t as, unw_word_t *dyn_info_list_addr,
return 0;
}
+#define PAGE_SIZE 4096
+#define PAGE_START(a) ((a) & ~(PAGE_SIZE-1))
+
+static int mem_validate_pipe[2] = {-1, -1};
+
+#ifdef HAVE_PIPE2
+static inline void
+do_pipe2 (int pipefd[2])
+{
+ pipe2 (pipefd, O_CLOEXEC | O_NONBLOCK);
+}
+#else
+static inline 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 inline void
+do_pipe2 (int pipefd[2])
+{
+ pipe (pipefd);
+ set_pipe_flags(pipefd[0]);
+ set_pipe_flags(pipefd[1]);
+}
+#endif
+
+static inline void
+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]);
+
+ do_pipe2 (mem_validate_pipe);
+}
+
+ALWAYS_INLINE
+static int
+write_validate (void *addr)
+{
+ int ret = -1;
+ ssize_t bytes = 0;
+
+ do
+ {
+ char buf;
+ bytes = read (mem_validate_pipe[0], &buf, 1);
+ }
+ while ( errno == EINTR );
+
+ int valid_read = (bytes > 0 || errno == EAGAIN || errno == EWOULDBLOCK);
+ if (!valid_read)
+ {
+ // re-open closed pipe
+ open_pipe ();
+ }
+
+ do
+ {
+ ret = write (mem_validate_pipe[1], addr, 1);
+ }
+ while ( errno == EINTR );
+
+ return ret;
+}
+
+static int (*mem_validate_func) (void *addr, size_t len);
+static int msync_validate (void *addr, size_t len)
+{
+ if (msync (addr, len, MS_ASYNC) != 0)
+ {
+ return -1;
+ }
+
+ return write_validate (addr);
+}
+
+#ifdef HAVE_MINCORE
+static int mincore_validate (void *addr, size_t len)
+{
+ unsigned char mvec[2]; /* Unaligned access may cross page boundary */
+
+ /* mincore could fail with EAGAIN but we conservatively return -1
+ instead of looping. */
+ if (mincore (addr, len, (unsigned char *)mvec) != 0)
+ {
+ return -1;
+ }
+
+ return write_validate (addr);
+}
+#endif
+
+/* Initialise memory validation method. On linux kernels <2.6.21,
+ mincore() returns incorrect value for MAP_PRIVATE mappings,
+ such as stacks. If mincore() was available at compile time,
+ check if we can actually use it. If not, use msync() instead. */
+HIDDEN void
+tdep_init_mem_validate (void)
+{
+ open_pipe ();
+
+#ifdef HAVE_MINCORE
+ unsigned char present = 1;
+ unw_word_t addr = PAGE_START((unw_word_t)&present);
+ unsigned char mvec[1];
+ int ret;
+ while ((ret = mincore ((void*)addr, PAGE_SIZE, (unsigned char *)mvec)) == -1 &&
+ errno == EAGAIN) {}
+ if (ret == 0)
+ {
+ Debug(1, "using mincore to validate memory\n");
+ mem_validate_func = mincore_validate;
+ }
+ else
+#endif
+ {
+ Debug(1, "using msync to validate memory\n");
+ mem_validate_func = msync_validate;
+ }
+}
+
+/* Cache of already validated addresses */
+#define NLGA 4
+#if defined(HAVE___CACHE_PER_THREAD) && HAVE___CACHE_PER_THREAD
+// thread-local variant
+static _Thread_local unw_word_t last_good_addr[NLGA];
+static _Thread_local int lga_victim;
+
+static int
+is_cached_valid_mem(unw_word_t addr)
+{
+ int i;
+ for (i = 0; i < NLGA; i++)
+ {
+ if (addr == last_good_addr[i])
+ return 1;
+ }
+ return 0;
+}
+
+static void
+cache_valid_mem(unw_word_t addr)
+{
+ int i, victim;
+ victim = lga_victim;
+ for (i = 0; i < NLGA; i++) {
+ if (last_good_addr[victim] == 0) {
+ last_good_addr[victim] = addr;
+ return;
+ }
+ victim = (victim + 1) % NLGA;
+ }
+
+ /* All slots full. Evict the victim. */
+ last_good_addr[victim] = addr;
+ victim = (victim + 1) % NLGA;
+ lga_victim = victim;
+}
+
+#else
+// global, thread safe variant
+static _Atomic unw_word_t last_good_addr[NLGA];
+static _Atomic int lga_victim;
+
+static int
+is_cached_valid_mem(unw_word_t addr)
+{
+ int i;
+ for (i = 0; i < NLGA; i++)
+ {
+ if (addr == atomic_load(&last_good_addr[i]))
+ return 1;
+ }
+ return 0;
+}
+
+static void
+cache_valid_mem(unw_word_t addr)
+{
+ int i, victim;
+ victim = atomic_load(&lga_victim);
+ unw_word_t zero = 0;
+ for (i = 0; i < NLGA; i++) {
+ if (atomic_compare_exchange_strong(&last_good_addr[victim], &zero, addr)) {
+ return;
+ }
+ victim = (victim + 1) % NLGA;
+ }
+
+ /* All slots full. Evict the victim. */
+ atomic_store(&last_good_addr[victim], addr);
+ victim = (victim + 1) % NLGA;
+ atomic_store(&lga_victim, victim);
+}
+#endif
+
+static int
+validate_mem (unw_word_t addr)
+{
+ size_t len;
+
+ if (PAGE_START(addr + sizeof (unw_word_t) - 1) == PAGE_START(addr))
+ len = PAGE_SIZE;
+ else
+ len = PAGE_SIZE * 2;
+
+ addr = PAGE_START(addr);
+
+ if (addr == 0)
+ return -1;
+
+ if (is_cached_valid_mem(addr))
+ return 0;
+
+ if (mem_validate_func ((void *) addr, len) == -1)
+ return -1;
+
+ cache_valid_mem(addr);
+
+ return 0;
+}
+
static int
access_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *val, int write,
void *arg)
{
- if (write)
+ if (unlikely (write))
{
Debug (16, "mem[%lx] <- %lx\n", addr, *val);
*(unw_word_t *) addr = *val;
}
else
{
+ /* validate address */
+ const struct cursor *c = (const struct cursor *)arg;
+ if (likely (c != NULL) && unlikely (c->validate)
+ && unlikely (validate_mem (addr))) {
+ Debug (16, "mem[%016lx] -> invalid\n", addr);
+ return -1;
+ }
*val = *(unw_word_t *) addr;
Debug (16, "mem[%lx] -> %lx\n", addr, *val);
}
@@ -103,7 +345,7 @@ access_reg (unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, int write,
void *arg)
{
unw_word_t *addr;
- unw_tdep_context_t *uc = arg;
+ unw_tdep_context_t *uc = ((struct cursor *)arg)->uc;
if (unw_is_fpreg (reg))
goto badreg;
@@ -132,7 +374,7 @@ static int
access_fpreg (unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val,
int write, void *arg)
{
- unw_tdep_context_t *uc = arg;
+ unw_tdep_context_t *uc = ((struct cursor *)arg)->uc;
unw_fpreg_t *addr;
if (!unw_is_fpreg (reg))
diff --git a/src/aarch64/Ginit_local.c b/src/aarch64/Ginit_local.c
index 3f0080ad..4a055fb0 100644
--- a/src/aarch64/Ginit_local.c
+++ b/src/aarch64/Ginit_local.c
@@ -47,7 +47,9 @@ unw_init_local_common (unw_cursor_t *cursor, unw_context_t *uc, unsigned use_pre
Debug (1, "(cursor=%p)\n", c);
c->dwarf.as = unw_local_addr_space;
- c->dwarf.as_arg = uc;
+ c->dwarf.as_arg = c;
+ c->uc = uc;
+ c->validate = 0;
return common_init (c, use_prev_instr);
}
diff --git a/src/aarch64/Ginit_remote.c b/src/aarch64/Ginit_remote.c
index 26d11ba9..e300173c 100644
--- a/src/aarch64/Ginit_remote.c
+++ b/src/aarch64/Ginit_remote.c
@@ -39,7 +39,16 @@ unw_init_remote (unw_cursor_t *cursor, unw_addr_space_t as, void *as_arg)
Debug (1, "(cursor=%p)\n", c);
c->dwarf.as = as;
- c->dwarf.as_arg = as_arg;
+ if (as == unw_local_addr_space)
+ {
+ c->dwarf.as_arg = c;
+ c->uc = as_arg;
+ }
+ else
+ {
+ c->dwarf.as_arg = as_arg;
+ c->uc = 0;
+ }
return common_init (c, 0);
#endif /* !UNW_LOCAL_ONLY */
}
diff --git a/src/aarch64/Gresume.c b/src/aarch64/Gresume.c
index 2cc16136..445bac70 100644
--- a/src/aarch64/Gresume.c
+++ b/src/aarch64/Gresume.c
@@ -34,7 +34,7 @@ aarch64_local_resume (unw_addr_space_t as, unw_cursor_t *cursor, void *arg)
{
#ifdef __linux__
struct cursor *c = (struct cursor *) cursor;
- unw_tdep_context_t *uc = c->dwarf.as_arg;
+ unw_tdep_context_t *uc = c->uc;
if (c->sigcontext_format == AARCH64_SCF_NONE)
{
diff --git a/src/aarch64/Gstep.c b/src/aarch64/Gstep.c
index fdf64a73..92e2a666 100644
--- a/src/aarch64/Gstep.c
+++ b/src/aarch64/Gstep.c
@@ -70,7 +70,7 @@ aarch64_handle_signal_frame (unw_cursor_t *cursor)
c->sigcontext_sp = c->dwarf.cfa;
c->sigcontext_pc = c->dwarf.ip;
- if (ret)
+ if (ret > 0)
{
c->sigcontext_format = AARCH64_SCF_LINUX_RT_SIGFRAME;
sc_addr = sp_addr + sizeof (siginfo_t) + LINUX_UC_MCONTEXT_OFF;
@@ -134,14 +134,30 @@ int
unw_step (unw_cursor_t *cursor)
{
struct cursor *c = (struct cursor *) cursor;
+ int validate = c->validate;
int ret;
Debug (1, "(cursor=%p, ip=0x%016lx, cfa=0x%016lx))\n",
c, c->dwarf.ip, c->dwarf.cfa);
+ /* Validate all addresses before dereferencing. */
+ c->validate = 1;
+
/* Check if this is a signal frame. */
- if (unw_is_signal_frame (cursor) > 0)
+ ret = unw_is_signal_frame (cursor);
+ if (ret > 0)
return aarch64_handle_signal_frame (cursor);
+ else if (unlikely (ret < 0))
+ {
+ /* IP points to non-mapped memory. */
+ /* This is probably SIGBUS. */
+ /* Try to load LR in IP to recover. */
+ Debug(1, "Invalid address found in the call stack: 0x%lx\n", c->dwarf.ip);
+ dwarf_get (&c->dwarf, c->dwarf.loc[UNW_AARCH64_X30], &c->dwarf.ip);
+ }
+
+ /* Restore default memory validation state */
+ c->validate = validate;
ret = dwarf_step (&c->dwarf);
Debug(1, "dwarf_step()=%d\n", ret);