Bug 23252

Summary: C++ bug (please see the body)
Product: Base System Reporter: osa <osa>
Component: gnuAssignee: David E. O'Brien <obrien>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: Unspecified   
Hardware: Any   
OS: Any   

Description osa 2000-12-03 23:40:01 UTC
	C++ bug. Please see the source code.

How-To-Repeat: 
	Here is a special code.

/*--------------------------------------------------------*/

#include <stdio.h>
#include <assert.h>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

#define Debug(x)	printf x

extern "C" {
typedef void *(*_THR_C_FUNC)(void *args);
}
typedef void *(*_THR_FUNC)(void *args);

/*-----------------------------------------------------------------*/
class    Mutex
{
public:
  Mutex() { assert(::pthread_mutex_init(&this->lock_, 0) == 0); }
  ~Mutex (void) { assert(::pthread_mutex_destroy(&this->lock_)==0); }
  int acquire (void) { return ::pthread_mutex_lock(&this->lock_); }
  int release (void) { return ::pthread_mutex_unlock (&this->lock_); }
  pthread_mutex_t lock_;
};

/*-----------------------------------------------------------------*/
class Condition
{
public:
  Condition (Mutex &m);
  ~Condition (void);
  int wait (void);
  void signal (void);
protected:
  pthread_cond_t cond_;
  Mutex &mutex_;
};

Condition::Condition (Mutex &m) : mutex_ (m)
{
  assert (pthread_cond_init(&this->cond_, 0) == 0);
}

Condition::~Condition (void)
{
   
    while(::pthread_cond_destroy(&this->cond_) == -1 && errno == EBUSY)
      {
        assert(::pthread_cond_broadcast(&this->cond_) == 0);
#ifdef __linux__
	::sched_yield ();
#else
        ::pthread_yield();
#endif
      }
}

int Condition::wait (void)
{
  return ::pthread_cond_wait(&this->cond_, &this->mutex_.lock_);
}

void Condition::signal (void)
{
  assert(::pthread_cond_signal(&this->cond_) == 0);
}

/*-----------------------------------------------------------------*/
class Guard
{
public:
  Guard (Mutex &l);
  ~Guard (void);
private:
  Mutex *lock_;
};
Guard::Guard (Mutex &l)
  : lock_ (&l)
{
  this->lock_->acquire ();
}
Guard::~Guard (void)
{
  this->lock_->release ();
}

/*-----------------------------------------------------------------*/
class _Base_Thread_Adapter
{
public:
  _Base_Thread_Adapter (_THR_FUNC user_func, void *arg);
  void *invoke (void);
  _THR_C_FUNC entry_point (void) { return entry_point_; }

private:
  _THR_FUNC user_func_;
  void *arg_;
  _THR_C_FUNC entry_point_;
};

extern "C" void * _thread_adapter (void *args)
{
  _Base_Thread_Adapter *thread_args = (_Base_Thread_Adapter*)args;
  void *status = thread_args->invoke ();
  return status;
}

_Base_Thread_Adapter::_Base_Thread_Adapter (_THR_FUNC user_func, void *arg)
  : user_func_ (user_func), arg_ (arg), entry_point_ (_thread_adapter)
{
}

void *
_Base_Thread_Adapter::invoke (void)
{
  void *(*func)(void *) = this->user_func_;
  void *arg = this->arg_;
  delete this;
  return func(arg);
}

/*-----------------------------------------------------------------*/
class SS {
  public:
    void spawn();
    static void run();
    static void *WThread( void *data );
};
  
/*---------------------------------------------------------------------*/
static Mutex CMutex;
static Condition Cond(CMutex);
static Mutex m1;

/*---------------------------------------------------------------------*/
#define REL(m,n) assert(m.release() != -1)
#define ACQ(m,n) assert(m.acquire() != -1)
      
/*---------------------------------------------------------------------*/
void *
SS::WThread( void *data )
{
    Cond.signal();
    Debug(("run thread...\n"));
    SS::run();
    Debug(("thread ended\n"));
    return NULL;
}

/*---------------------------------------------------------------------*/
int thr_create (_THR_FUNC func, void *args)
{
  _Base_Thread_Adapter *thread_args;
  thread_args  = new  _Base_Thread_Adapter(func, args);
  pthread_attr_t attr;
  if (::pthread_attr_init (&attr) != 0)
      return -1;
  ::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_t thr_id;
  assert( ::pthread_create (&thr_id, &attr,
                    thread_args->entry_point(), thread_args) == 0 );
  ::pthread_attr_destroy (&attr);
}
/*---------------------------------------------------------------------*/
void
SS::spawn()
{
#ifdef BAD
    int rc;
    Guard guard(m1);   // !!!
#else
    Guard guard(m1);   // !!!
    int rc;
#endif
    pthread_attr_t attr;
    if (::pthread_attr_init (&attr) != 0) return;
    ::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    thr_create(SS::WThread, (void *)0);
    ::pthread_attr_destroy (&attr);
    ACQ(CMutex, "CMutex");
    rc = Cond.wait();
    if( rc == -1 )
      Debug(("Cond wait failed: %s\n", strerror(errno)));
    REL(CMutex, "CMutex"); 
}

/*---------------------------------------------------------------------*/
void
SS::run()
{
    string s;	// !!!
    string s1;  // !!!
    sleep(1);
}

/*=====================================================================*/
static void sp_call(SS *ss)
{
    string s;	// !!!
    ss->spawn();
}

/*------------------------------------------------------------------*/
int main(int argc, char **argv)
{
    SS ss;
    sp_call(&ss);
    sleep(2);
    Debug(("Exitting...\n"));
    sleep(3);
    return 0;
}

/*--------------------------------------------------------*/

And here is a makefile for gmake:

Goal: bad good

bad: tt.cpp
	$(CXX) -DBAD tt.cpp -pthread  -g -o bad
	$(CXX) -S -DBAD tt.cpp -pthread  -g -o bad.s

good: tt.cpp
	$(CXX) tt.cpp -pthread  -g -o good
	$(CXX) -S tt.cpp -pthread  -g -o good.s


Put the source code in the one directory and say:
$ gmake
After build a programms bad and good, try to run:

% ./good
run thread...
thread ended
Exitting...
% ./bad
run thread...
Segmentation fault (core dumped)
Comment 1 Maxim Sobolev freebsd_committer freebsd_triage 2000-12-05 17:38:50 UTC
Responsible Changed
From-To: gnats-admin->obrien

Over to c++ maintainer.
Comment 2 eischen 2000-12-15 17:02:11 UTC
I've analyzed this PR and am the opinion that it is not a threads library
bug.  It seems to have something to do with either the compiler (libgcc2)
or the implementation of string in libstdc++.  In the sample program included
with the original PR submittal, there are the following functions:

void
SS::run()
{
    string s;   // !!!
    string s1;  // !!!
    sleep(1);
}

static void sp_call(SS *ss)
{
    string s;   // !!!
    ss->spawn();
}

SS::run() is called from a thread that is created by the "ss->spawn()" call in
sp_call().  sp_call initializes/allocates the local string s, then creates a
thread that calls SS::run().  SS::run() is called which also initializes/allocates
2 strings (s and s1).  The thread then sleeps and a thread switch back to the
main thread occurs.  ss->spawn returns and the local string in sp_call() is
then finalized.  When the spawned thread returns from the sleep it also tries
to finalize the two strings (s and s1).  But it seems that the finalization
of the string in sp_call() has corrupted one of the strings in SS::run().
Comment 3 johnnyteardrop 2000-12-21 19:16:42 UTC
enclosed is another test program for this problem. execute it a bunch of 
times.  you'll see three or four different things depending on what causes 
death first:

these two mean an exception warped from one thread to another:

$ ./testex
B catches Z
Abort trap (core dumped)
or:
$ ./testex
A catches Y
Abort trap (core dumped)

this means that unexpected() was called by the runtime:

$ ./testex
unexpected exception thrown
Abort trap (core dumped)

and this means that the bit of unwinding code blew it:

$ ./testex
Segmentation fault (core dumped)

it dies in:

        leal 4(%edi),%edx
        movl (%edx),%eax
        addl $4,%eax
        movl (%eax),%edx
        movl (%edx),%ecx
        movl %ecx,(%eax)

because edx is zero.

/*
* testex.cpp
*
* this dumb program spins off a few threads that just throw and catch some
* exceptions.  it's supposed to run silently forever.  it'll print some 
stuff
* and exit if one of two things happens:
*
* 1) "unexpected()" is invoked by the runtime
* 2) the wrong exception is caught in a thread
*/

#include <stdio.h> // 11 15 37
#include <stdlib.h>
#include <pthread.h>
#include <new>
#include <exception>


// this is the exception thrown by instances of threadA
class exZ : public std::exception {

public:

    inline exZ(void) : std::exception() { }
    virtual ~exZ(void);

}; // class exZ

exZ::~exZ(
    void
    )
{
}


// this is the exception thrown by instances of threadB
class exY : public std::exception {

public:

    inline exY(void) : std::exception() { }
    virtual ~exY(void);

}; // class exY

exY::~exY(
    void
    )
{
}


// this is a synchronization device that mixes up the scheduling of threadA
// instances
class Flipper {

public:
    Flipper(void);
    ~Flipper(void);
    void Acquire(void);
    void Release(void);

private:
    pthread_t			 _owner;
    pthread_mutex_t		 _m;
    pthread_t			 _wanter;
    pthread_cond_t		*_wc;

}; // class Flipper


Flipper::Flipper(
    void
    ) : _owner(static_cast<pthread_t>(0)), 
_wanter(static_cast<pthread_t>(0)),
	_wc(NULL)
{
    pthread_mutex_init(&_m, NULL);
}


Flipper::~Flipper(
    void
    )
{
    pthread_mutex_destroy(&_m);
}


void Flipper::Acquire(
    void
    )
{
    pthread_t			 me = pthread_self();

    if (_owner == me)
	abort();

    pthread_mutex_lock(&_m);

    if (_owner == 0) {

	_owner = me;

    } else {

	pthread_cond_t		 myc;

	pthread_cond_init(&myc, NULL);

	_wanter = me;
	_wc = &myc;

	while (_owner != me)
	    pthread_cond_wait(&myc, &_m);

	pthread_cond_destroy(&myc);

    }

    pthread_mutex_unlock(&_m);
}


void Flipper::Release(
    void
    )
{
    pthread_mutex_lock(&_m);

    if (_wanter == 0) {

	_owner = static_cast<pthread_t>(0);

    } else {

	pthread_cond_t		*c = _wc;

	_owner = _wanter;
	_wanter = 0;
	_wc = NULL;

	pthread_cond_signal(c);

    }

    pthread_mutex_unlock(&_m);
}


// a stack object to automate flippage
class AutoFlip {

public:
    inline AutoFlip(Flipper *flip) : _flip(flip) {
	_flip->Acquire();
    }

    inline ~AutoFlip(void) {
	_flip->Release();
    }

private:
    Flipper			*_flip;

}; // class AutoFlip



// threadA's main processing loop.  pretty dumb.  it just trades off with
// another instance of threadA.
static void *threadAMain(
    void			*arg
    ) throw()
{
    Flipper			*flip = reinterpret_cast<Flipper *>(arg);

    do {

	try {
	    AutoFlip		 f(flip);

	    //printf("A throws Z\n");
	    throw exZ();

	} catch (const exZ &z) {
	    //printf("A catches Z\n");
	} catch (const exY &y) {
	    printf("A catches Y\n");
	    abort();
	}

    } while(true);

    return NULL;
}



// threadB's main processing loop.  even dumber.  it just throws exceptions
// at itself.
static void *threadBMain(
    void			*arg
    ) throw()
{
    do {

	try {
	    //printf("B throws Y\n");
	    throw exY();
	} catch (const exY &y) {
	    //printf("B catches Y\n");
	} catch (const exZ &z) {
	    printf("B catches Z\n");
	    abort();
	}

    } while(true);

    return NULL;
}


// handle unexpected exceptions (there shouldn't be any)
static void unexpectedHandler(
    void
    )
{
    printf("unexpected exception thrown\n");
    terminate();
}


// program entry point.  the join() will never complete, so there's no point
// in cleaning up the flipper.
int main(
    int				 argc,
    char			*argv[]
    ) throw()
{
    Flipper			*f;
    pthread_t			 thread;

    set_unexpected(unexpectedHandler);

    f = new Flipper();
    pthread_create(&thread, NULL, threadAMain, f);
    pthread_create(&thread, NULL, threadAMain, f);
    pthread_create(&thread, NULL, threadBMain, NULL);

    pthread_join(thread, NULL);

    return 0;
}
_________________________________________________________________
Get your FREE download of MSN Explorer at http://explorer.msn.com
Comment 4 ak03 2000-12-21 22:50:34 UTC
The following hack stops test case from coredumping.

a) cd /usr/src/gnu/lib/libgcc_r
b) change CFLAGS+=-D_PTHREADS ÎÁ CFLAGS+=-D_PTHREADS -DGTHREAD_USE_WEAK=1
c) make all install
d) cp /usr/lib/libgcc/libgcc_r_pic.a /usr/lib/libgcc/libgcc_pic.a
e) cd /usr/src/gnu/lib/libstdc++; make clean all install

David, why can't we build libgcc_pic.a with -D_PTHREADS -DGTHREAD_USE_WEAK=1
always and get rid of libgcc_r_pic.a altogether? I just finished buildworld
after doing just that and everything seems to work so far. I will continue my
testing and will let you know if something breaks.
Comment 5 David E. O'Brien freebsd_committer freebsd_triage 2000-12-21 23:05:32 UTC
On Thu, Dec 21, 2000 at 05:50:34PM -0500, Alexander N. Kabaev wrote:
> David, why can't we build libgcc_pic.a with -D_PTHREADS -DGTHREAD_USE_WEAK=1
> always and get rid of libgcc_r_pic.a altogether? I just finished buildworld

I am working on something simular.
Comment 6 David E. O'Brien freebsd_committer freebsd_triage 2001-01-06 07:35:36 UTC
State Changed
From-To: open->closed

fix committed to -current and will be MFC'ed on the fast track