Jeremy W. Sherman

stay a while, and listen

Surfing with ctags

Big code, little time, and you’ve work to do.

Where’s that function at? What’s in that struct?

There’s an app for that: ctags.

In Brief

The short version:

  • Install exuberant ctags.
  • ctags -R . in the project’s top-level directory to generate the tags file.
  • vim $file from within the same directory as your tags file.
    • Alternatively: vim -t $tagname to jump straight to that tag.
  • CTRL-] while cursor in a tag (roughly fn/var/other name) pushes your current location then jumps to definition of the tag you were on.
  • CTRL-t pops back up the tag stack, so you can continue where you left off.
  • :help tags for more, but that’s about all you need.

(The astute vimmer will now note that the ^]/^t pair is the same one you use to jump around in vim help files. Handy, that.)

If you forget this, just man ctags, /HOW TO USE, and read that very brief section.

Ctags!

You don’t want just any ctags. You want the exuberant kind. Don’t ask why, just trust me: brew install ctags and get yourself some exuberant ctags.

Caramel Apple Tarball

To demonstrate, nab yourself delicious Apple-flavored libc. This particular variety is the one included with 10.8.3. tar xzf Libc-825.26.tar.gz, cd Libc-825.26 into the resulting directory, and wow that’s a good chunk of stuff.

Surfing Ctags

Now it’s time for ctags. First, we generate the tags file by walking the entire directory hierarchy we’re sitting at the root of. Don’t worry, it’s not so bad as all that, just a -Recurse flag and a . directory to kick things off:

1
2
3
Libc-825.26% ctags -R .
Libc-825.26% wc -l tags
   12832 tags

Thassa lotta tags.

Let’s jump into the thick of it. Fire up vim at the definition of pthread_cond_signal:

1
Libc-825.26% vi -t pthread_cond_signal

We find ourselves with cursor blinking right on the p at the start of pthread_cond_signal:

1
2
3
4
5
6
7
8
/*
 * Signal a condition variable, waking only one thread.
 */
int
pthread_cond_signal(pthread_cond_t *cond)
{
  return pthread_cond_signal_thread_np(cond, NULL);
}

This is where things would start to suck if we didn’t have ctags. The entire function is just a call to yet another function. The _np non-portable suffix makes me think maybe it will be somewhere else. Maybe they exiled all the non-portable functions to a different file? Who knows. Even better: we don’t care.

/_np, RET, and hit CTRL-] to jump right to that function:

1
2
3
4
5
6
7
8
9
/*
 * Signal a condition variable, waking a specified thread.
 */

int
pthread_cond_signal_thread_np(pthread_cond_t *ocond, pthread_t thread)
{
  npthread_cond_t * cond = (npthread_cond_t *)ocond;
  int sig = cond->sig;

Ooh, fun, we transition from an old-cond to a new-style struct. (Maybe that _np suffix was new pthreads this time.) What’s the difference between old and new? Let’s take a look-see and find out.

/np, n, RET, CTRL-]. Looks like the definition of npthread_cond_t lives right below the original pthread_cond_t struct definition, so comparing the two is easy. The new version looks to have swapped out most of the old guts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * Condition variables
 */
#define _PTHREAD_COND_T
typedef struct _pthread_cond
{
  long           sig;        /* Unique signature for this structure */
  pthread_lock_t lock;       /* Used for internal mutex on structure */
  uint32_t    waiters:15,   /* Number of threads waiting */
         sigspending:15,    /* Number of outstanding signals */
          pshared:2;
  struct _pthread_cond *next, *prev;  /* List of condition variables using mutex */
  struct _pthread_mutex *busy; /* mutex associated with variable */
  semaphore_t    sem;        /* Kernel semaphore */
} pthread_cond_t;

for free space and a different approach to getting things done:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _npthread_cond
{
  long           sig;        /* Unique signature for this structure */
  pthread_lock_t lock;       /* Used for internal mutex on structure */
  uint32_t    rfu:29,       /* not in use*/
          misalign: 1,  /* structure is not aligned to 8 byte boundary */
          pshared:2;
  struct _npthread_mutex *busy; /* mutex associated with variable */
  uint32_t    c_seq[3];
#if defined(__LP64__)
  uint32_t    reserved[3];
#endif /* __LP64__ */
} npthread_cond_t;

There’s also LP64 support and some alignment games. Fun times.

Well, that’s enough struct-gazing, we can pop back on up to where we were with CTRL-T and resume looking at the implementation of pthread_cond_signal_thread_np, right where we left off.

1
2
int
pthread_cond_signal_thread_np(pthread_cond_t *ocond, pthread_t thread)

I bet it’d be interesting to see what happens when that thread argument is NULL, as it is in a standard pthread_cond_signal call.

And when might it be called with a non-NULL value?

Curious and curiouser.

I leave you to it, dear reader, with exuberant ctags frolicking and wagging tail at your side.

Added 2013-07-26T15:03:08Z-0400: This post is an introductory walkthrough. Once you’ve got the hang of the basics, check out the discussion at /r/vim for further pointers.