blob: d5bb1040898e6e04340270e50c8cad01a57891dd [file] [log] [blame]
/* Copyright (c) 2008-2010, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// This file is part of ThreadSanitizer, a dynamic data race detector.
// Author: Konstantin Serebryany.
// Author: Timur Iskhodzhanov.
//
// See ts_util.h for mode details.
#include "common_util.h"
#include "thread_sanitizer.h"
#include "ts_stats.h"
#include "ts_lock.h"
#include <stdarg.h>
FLAGS *G_flags = NULL;
#if defined(_MSC_VER)
#pragma comment(lib, "winmm.lib")
# ifdef TS_PIN
# include "pin.H"
# endif
namespace WINDOWS
{
// This is the way of including winows.h recommended by PIN docs.
#include<Windows.h>
}
int getpid() { return WINDOWS::GetCurrentProcessId(); }
#endif
#if defined(TS_VALGRIND)
size_t TimeInMilliSeconds() {
return VG_(read_millisecond_timer)();
}
#else
// TODO(kcc): implement this.
size_t TimeInMilliSeconds() {
#ifdef __GNUC__
return time(0) * 1000;
#else
return WINDOWS::timeGetTime();
#endif
}
#endif
Stats *G_stats;
bool GetNameAndOffsetOfGlobalObject(uintptr_t addr,
string *name, uintptr_t *offset) {
#ifdef TS_VALGRIND
const int kBufLen = 1023;
char buff[kBufLen+1];
PtrdiffT off;
if (VG_(get_datasym_and_offset)(addr, reinterpret_cast<Char*>(buff),
kBufLen, &off)) {
*name = buff;
*offset = off;
return true;
}
return false;
#else
return false;
#endif // TS_VALGRIND
}
#ifndef TS_VALGRIND
void GetThreadStack(int tid, uintptr_t *min_addr, uintptr_t *max_addr) {
*min_addr = 0xfffa;
*max_addr = 0xfffb;
}
#endif
static int n_errs_found;
void SetNumberOfFoundErrors(int n_errs) {
n_errs_found = n_errs;
}
int GetNumberOfFoundErrors() {
return n_errs_found;
}
#if !defined(TS_VALGRIND) && !defined(TS_LLVM)
FILE *G_out = stderr;
#endif
#ifdef TS_LLVM
FILE *G_out;
#endif
static string RemoveUnsupportedFormat(const char *str) {
#ifdef _MSC_VER
// replace "%'" with "%"
string res;
size_t n = strlen(str);
if (n == 0) {
return "";
}
res.reserve(n);
res.push_back(str[0]);
for (size_t i = 1; i < n; i++) {
if (str[i] == '\'' && *res.rbegin() == '%') continue;
res.push_back(str[i]);
}
return res;
#else
return str;
#endif
}
void Printf(const char *format, ...) {
#ifdef TS_VALGRIND
va_list args;
va_start(args, format);
VG_(vprintf)(format, args);
va_end(args);
#else
va_list args;
va_start(args, format);
vfprintf(G_out, RemoveUnsupportedFormat(format).c_str(), args);
fflush(G_out);
va_end(args);
#endif
}
// Like Print(), but prepend each line with ==XXXXX==,
// where XXXXX is the pid.
void Report(const char *format, ...) {
int buff_size = 1024*16;
char *buff = new char[buff_size];
CHECK(buff);
DCHECK(G_flags);
va_list args;
while (1) {
va_start(args, format);
int ret = vsnprintf(buff, buff_size,
RemoveUnsupportedFormat(format).c_str(), args);
va_end(args);
if (ret < buff_size) break;
delete [] buff;
buff_size *= 2;
buff = new char[buff_size];
CHECK(buff);
// Printf("Resized buff: %d\n", buff_size);
}
char pid_buff[100];
snprintf(pid_buff, sizeof(pid_buff), "==%d== ", getpid());
string res;
#ifndef TS_LLVM
int len = strlen(buff);
#else
int len = __real_strlen(buff);
#endif
bool last_was_new_line = true;
for (int i = 0; i < len; i++) {
if (G_flags->show_pid && last_was_new_line)
res += pid_buff;
last_was_new_line = (buff[i] == '\n');
res += buff[i];
}
delete [] buff;
Printf("%s", res.c_str());
}
long my_strtol(const char *str, char **end, int base) {
#ifdef TS_VALGRIND
if (base == 16 || (base == 0 && str && str[0] == '0' && str[1] == 'x')) {
return VG_(strtoll16)((Char*)str, (Char**)end);
}
return VG_(strtoll10)((Char*)str, (Char**)end);
#else
return strtoll(str, end, base);
#endif
}
// Not thread-safe. Need to make it thread-local if we allow
// malloc to be called concurrently.
MallocCostCenterStack g_malloc_stack;
size_t GetVmSizeInMb() {
#ifdef VGO_linux
const char *path ="/proc/self/statm"; // see 'man proc'
uintptr_t counter = G_stats->read_proc_self_stats++;
if (counter >= 1024 && ((counter & (counter - 1)) == 0))
Report("INFO: reading %s for %ld'th time\n", path, counter);
int fd = OpenFileReadOnly(path, false);
if (fd < 0) return 0;
char buff[128];
int n_read = read(fd, buff, sizeof(buff) - 1);
buff[n_read] = 0;
close(fd);
char *end;
size_t vm_size_in_pages = my_strtol(buff, &end, 10);
return vm_size_in_pages >> 8;
#else
return 0;
#endif
}
void OpenFileWriteStringAndClose(const string &file_name, const string &str) {
#ifdef TS_VALGRIND
SysRes sres = VG_(open)((const Char*)file_name.c_str(),
VKI_O_WRONLY|VKI_O_CREAT|VKI_O_TRUNC,
VKI_S_IRUSR|VKI_S_IWUSR);
if (sr_isError(sres)) {
Report("WARNING: can not open file %s\n", file_name.c_str());
exit(1);
}
int fd = sr_Res(sres);
write(fd, str.c_str(), str.size());
close(fd);
#else
CHECK(0);
#endif
}
//--------- Sockets ------------------ {{{1
#if defined(TS_PIN) && defined(__GNUC__)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
FILE *OpenSocketForWriting(const string &host_and_port) {
size_t col = host_and_port.find(":");
if (col == string::npos) return NULL;
string host = host_and_port.substr(0, col);
string port_str = host_and_port.substr(col + 1);
int sockfd;
struct sockaddr_in serv_addr;
struct hostent *server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return NULL;
server = gethostbyname(host.c_str());
if (server == 0) return NULL;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy((char *)&serv_addr.sin_addr.s_addr,
(char *)server->h_addr,
server->h_length);
serv_addr.sin_port = htons(atoi(port_str.c_str()));
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
return NULL;
return fdopen(sockfd, "w");
}
#else
FILE *OpenSocketForWriting(const string &host_and_port) {
return NULL; // unimplemented.
}
#endif
//--------- TSLock ------------------ {{{1
#ifdef _MSC_VER
//# define TS_LOCK_PIPE
# define TS_LOCK_PIN
#else
# define TS_LOCK_FUTEX
#endif
#if defined(TS_LOCK_PIPE) && defined(TS_PIN)
#ifdef __GNUC__
#include <unistd.h>
// Lock based on pipe's send/receive. The idea (but not the code)
// is shamelessly stolen from valgrind's /coregrind/m_scheduler/sema.c
struct TSLock::Rep {
bool held;
char pipe_char;
int pipe_fd[2];
void Write() {
char buf[2];
buf[0] = pipe_char;
buf[1] = 0;
int res = write(pipe_fd[1], buf, 1);
CHECK(res == 1);
}
bool Read() {
char buf[2];
buf[0] = 0;
buf[1] = 0;
int res = read(pipe_fd[0], buf, 1);
if (res != 1)
return false;
//Printf("rep::Read: %c\n", buf[0]);
pipe_char++;
if (pipe_char == 'Z' + 1) pipe_char = 'A';
return true;
}
void Open() {
CHECK(0 == pipe(pipe_fd));
CHECK(pipe_fd[0] != pipe_fd[1]);
pipe_char = 'A';
}
void Close() {
close(pipe_fd[0]);
close(pipe_fd[1]);
}
};
#elif defined(_MSC_VER)
struct TSLock::Rep {
bool held;
char pipe_char;
WINDOWS::HANDLE pipe_fd[2];
void Write() {
char buf[2];
buf[0] = pipe_char;
buf[1] = 0;
WINDOWS::DWORD n_written = 0;
int res = WINDOWS::WriteFile(pipe_fd[1], buf, 1, &n_written, NULL);
CHECK(res != 0 && n_written == 1);
}
bool Read() {
char buf[2];
buf[0] = 0;
buf[1] = 0;
WINDOWS::DWORD n_read = 0;
int res = WINDOWS::ReadFile(pipe_fd[0], buf, 1, &n_read, NULL);
if (res == 0 && n_read == 0)
return false;
//Printf("rep::Read: %c\n", buf[0]);
pipe_char++;
if (pipe_char == 'Z' + 1) pipe_char = 'A';
return true;
}
void Open() {
CHECK(WINDOWS::CreatePipe(&pipe_fd[0], &pipe_fd[1], NULL, 0));
CHECK(pipe_fd[0] != pipe_fd[1]);
pipe_char = 'A';
}
void Close() {
WINDOWS::CloseHandle(pipe_fd[0]);
WINDOWS::CloseHandle(pipe_fd[1]);
}
};
#endif
TSLock::TSLock() {
rep_ = new Rep;
rep_->held = false;
rep_->Open();
rep_->Write();
}
TSLock::~TSLock() {
rep_->Close();
}
void TSLock::Lock() {
while(rep_->Read() == false)
;
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
rep_->Write();
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // __GNUC__ & TS_LOCK_PIPE
#if defined(TS_LOCK_PIN) && defined(TS_PIN)
#include "pin.H"
struct TSLock::Rep {
PIN_LOCK lock;
bool held;
};
TSLock::TSLock() {
rep_ = new Rep();
rep_->held = false;
InitLock(&rep_->lock);
}
TSLock::~TSLock() {
delete rep_;
}
void TSLock::Lock() {
GetLock(&rep_->lock, __LINE__);
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
ReleaseLock(&rep_->lock);
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // TS_LOCK_PIN
#if defined(TS_WRAP_PTHREAD_LOCK)
#include "tsan_rtl_wrap.h"
struct TSLock::Rep {
pthread_mutex_t lock;
bool held;
};
TSLock::TSLock() {
rep_ = new Rep();
rep_->held = false;
__real_pthread_mutex_init(&rep_->lock, NULL);
}
TSLock::~TSLock() {
__real_pthread_mutex_destroy(&rep_->lock);
delete rep_;
}
void TSLock::Lock() {
__real_pthread_mutex_lock(&rep_->lock);
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
__real_pthread_mutex_unlock(&rep_->lock);
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // TS_LLVM
#if defined(TS_LOCK_FUTEX) && defined(__GNUC__) && \
(defined (TS_PIN) || defined (TS_LLVM))
#include <linux/futex.h>
#include <sys/time.h>
#include <syscall.h>
// Simple futex-based lock.
// The idea is taken from "Futexes Are Tricky" by Ulrich Drepper
TSLock::TSLock() {
rep_ = 0;
ANNOTATE_BENIGN_RACE(&rep_, "Benign race on TSLock::rep_");
ANNOTATE_RWLOCK_CREATE(this);
}
TSLock::~TSLock() {
ANNOTATE_RWLOCK_DESTROY(this);
DCHECK(rep_ == 0);
}
void TSLock::Lock() {
int *p = (int*)&rep_;
const int kSpinCount = 100;
DCHECK(kSpinCount > 0);
int c;
for (int i = 0; i < kSpinCount; i++) {
c = __sync_val_compare_and_swap(p, 0, 1);
if (c == 0) break;
}
if (c == 0) {
// The mutex was unlocked. Now it's ours. Done.
ANNOTATE_RWLOCK_ACQUIRED(this, /*is_w*/true);
return;
}
DCHECK(c == 1 || c == 2);
// We are going to block on this lock. Make sure others know that.
if (c != 2) {
c = __sync_lock_test_and_set(p, 2);
}
// Block.
int n_waits = 0;
while (c != 0) {
syscall(SYS_futex, p, FUTEX_WAIT, 2, 0, 0, 0);
n_waits++;
c = __sync_lock_test_and_set(p, 2);
}
ANNOTATE_RWLOCK_ACQUIRED(this, /*is_w*/true);
G_stats->futex_wait += n_waits;
}
void TSLock::Unlock() {
ANNOTATE_RWLOCK_RELEASED(this, /*is_w*/true);
int *p = (int*)&rep_;
DCHECK(*p == 1 || *p == 2);
int c = __sync_sub_and_fetch(p, 1);
DCHECK(c == 0 || c == 1);
if (c == 1) {
*p = 0;
syscall(SYS_futex, p, FUTEX_WAKE, 1, 0, 0, 0);
}
}
void TSLock::AssertHeld() {
DCHECK(rep_);
}
#endif
//--------------- Atomics ----------------- {{{1
#if defined (_MSC_VER) && TS_SERIALIZED == 0
uintptr_t AtomicExchange(uintptr_t *ptr, uintptr_t new_value) {
return _InterlockedExchange((volatile WINDOWS::LONG*)ptr, new_value);
}
void ReleaseStore(uintptr_t *ptr, uintptr_t value) {
*(volatile uintptr_t*)ptr = value;
// TODO(kcc): anything to add here?
}
int32_t NoBarrier_AtomicIncrement(int32_t* ptr) {
return _InterlockedIncrement((volatile WINDOWS::LONG *)ptr);
}
int32_t NoBarrier_AtomicDecrement(int32_t* ptr) {
return _InterlockedDecrement((volatile WINDOWS::LONG *)ptr);
}
#endif // _MSC_VER && TS_SERIALIZED
//--------------- YIELD ----------------- {{{1
#if defined (_MSC_VER)
void YIELD() {
WINDOWS::Sleep(0);
}
#elif defined(TS_VALGRIND)
void YIELD() {
}
#elif defined(__GNUC__)
void YIELD() {
sched_yield();
}
#else
#error "Unknown config"
#endif
// end. {{{1
// vim:shiftwidth=2:softtabstop=2:expandtab:tw=80