//===-- asan_thread.cc ----------------------------------------------------===//
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer, an address sanity checker.
//
// Thread-related code.
//===----------------------------------------------------------------------===//
#include "asan_allocator.h"
#include "asan_interceptors.h"
#include "asan_stack.h"
#include "asan_thread.h"
#include "asan_thread_registry.h"
#include "asan_mapping.h"
#include "sanitizer_common/sanitizer_common.h"

namespace __asan {

AsanThread::AsanThread(LinkerInitialized x)
    : fake_stack_(x),
      malloc_storage_(x),
      stats_(x) { }

AsanThread *AsanThread::Create(u32 parent_tid, thread_callback_t start_routine,
                               void *arg, StackTrace *stack) {
  uptr PageSize = GetPageSizeCached();
  uptr size = RoundUpTo(sizeof(AsanThread), PageSize);
  AsanThread *thread = (AsanThread*)MmapOrDie(size, __FUNCTION__);
  thread->start_routine_ = start_routine;
  thread->arg_ = arg;

  const uptr kSummaryAllocSize = PageSize;
  CHECK_LE(sizeof(AsanThreadSummary), kSummaryAllocSize);
  AsanThreadSummary *summary =
      (AsanThreadSummary*)MmapOrDie(PageSize, "AsanThreadSummary");
  summary->Init(parent_tid, stack);
  summary->set_thread(thread);
  thread->set_summary(summary);

  return thread;
}

void AsanThreadSummary::TSDDtor(void *tsd) {
  AsanThreadSummary *summary = (AsanThreadSummary*)tsd;
  if (flags()->verbosity >= 1) {
    Report("T%d TSDDtor\n", summary->tid());
  }
  if (summary->thread()) {
    summary->thread()->Destroy();
  }
}

void AsanThread::Destroy() {
  if (flags()->verbosity >= 1) {
    Report("T%d exited\n", tid());
  }

  asanThreadRegistry().UnregisterThread(this);
  CHECK(summary()->thread() == 0);
  // We also clear the shadow on thread destruction because
  // some code may still be executing in later TSD destructors
  // and we don't want it to have any poisoned stack.
  ClearShadowForThreadStack();
  fake_stack().Cleanup();
  uptr size = RoundUpTo(sizeof(AsanThread), GetPageSizeCached());
  UnmapOrDie(this, size);
}

void AsanThread::Init() {
  SetThreadStackTopAndBottom();
  CHECK(AddrIsInMem(stack_bottom_));
  CHECK(AddrIsInMem(stack_top_ - 1));
  ClearShadowForThreadStack();
  if (flags()->verbosity >= 1) {
    int local = 0;
    Report("T%d: stack [%p,%p) size 0x%zx; local=%p\n",
           tid(), (void*)stack_bottom_, (void*)stack_top_,
           stack_top_ - stack_bottom_, &local);
  }
  fake_stack_.Init(stack_size());
  AsanPlatformThreadInit();
}

thread_return_t AsanThread::ThreadStart() {
  Init();
  if (flags()->use_sigaltstack) SetAlternateSignalStack();

  if (!start_routine_) {
    // start_routine_ == 0 if we're on the main thread or on one of the
    // OS X libdispatch worker threads. But nobody is supposed to call
    // ThreadStart() for the worker threads.
    CHECK(tid() == 0);
    return 0;
  }

  thread_return_t res = start_routine_(arg_);
  malloc_storage().CommitBack();
  if (flags()->use_sigaltstack) UnsetAlternateSignalStack();

  this->Destroy();

  return res;
}

void AsanThread::SetThreadStackTopAndBottom() {
  GetThreadStackTopAndBottom(tid() == 0, &stack_top_, &stack_bottom_);
  int local;
  CHECK(AddrIsInStack((uptr)&local));
}

void AsanThread::ClearShadowForThreadStack() {
  PoisonShadow(stack_bottom_, stack_top_ - stack_bottom_, 0);
}

const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset) {
  uptr bottom = 0;
  if (AddrIsInStack(addr)) {
    bottom = stack_bottom();
  } else {
    bottom = fake_stack().AddrIsInFakeStack(addr);
    CHECK(bottom);
    *offset = addr - bottom;
    return  (const char *)((uptr*)bottom)[1];
  }
  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
  u8 *shadow_bottom = (u8*)MemToShadow(bottom);

  while (shadow_ptr >= shadow_bottom &&
         *shadow_ptr != kAsanStackLeftRedzoneMagic) {
    shadow_ptr--;
  }

  while (shadow_ptr >= shadow_bottom &&
         *shadow_ptr == kAsanStackLeftRedzoneMagic) {
    shadow_ptr--;
  }

  if (shadow_ptr < shadow_bottom) {
    *offset = 0;
    return "UNKNOWN";
  }

  uptr* ptr = (uptr*)SHADOW_TO_MEM((uptr)(shadow_ptr + 1));
  CHECK(ptr[0] == kCurrentStackFrameMagic);
  *offset = addr - (uptr)ptr;
  return (const char*)ptr[1];
}

}  // namespace __asan
