From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 3096 invoked by alias); 19 Jun 2002 16:00:15 -0000 Mailing-List: contact gdb-help@sources.redhat.com; run by ezmlm Precedence: bulk List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-owner@sources.redhat.com Received: (qmail 2958 invoked from network); 19 Jun 2002 16:00:06 -0000 Received: from unknown (HELO zwingli.cygnus.com) (208.245.165.35) by sources.redhat.com with SMTP; 19 Jun 2002 16:00:06 -0000 Received: by zwingli.cygnus.com (Postfix, from userid 442) id 38A625EA11; Wed, 19 Jun 2002 11:00:04 -0500 (EST) From: Jim Blandy To: gdb@sources.redhat.com Subject: GDB support for thread-local storage Message-Id: <20020619160004.38A625EA11@zwingli.cygnus.com> Date: Wed, 19 Jun 2002 09:00:00 -0000 X-SW-Source: 2002-06/txt/msg00145.txt.bz2 I'd like to extend GDB to support thread-local variables. Richard Henderson, Ulrich Drepper and I have come up with some pieces of the solutions; pretty much everything outside GDB has been settled, but I'm still trying to figure out how GDB will pull the pieces together. This post describes: - the feature we're trying to support, - the parts we've worked out so far, and - the parts in GDB that I'm still trying to sort out. Some implementations of C and C++ support a ``__thread'' storage class, for variables that occupy distinct memory in distinct threads. For example, the definition: __thread int foo; declares an integer variable named ``foo'' which has a separate value and address in each thread, much as a variable declared ``auto'' has a separate value and address in each invocation of the function containing its declaration. Creating a new thread creates a new instance of ``foo'', and when the thread exits, the storage for ``foo'' is freed. Typically, a program includes an ``initialization image'' --- a block of memory containing the initial values for any thread-local variables it defines. When the program creates a new thread, the run-time system allocates a fresh block of memory for those thread-local variables, and copies the initialization image into it to give the variables their initialized values. A dynamically loaded library may also define thread-local variables. Some implementations delay allocating memory for such variables until the thread actually refers to them for the first time. This avoids the overhead of allocating and initializing the library's thread-local storage for all the threads present in a program when the library is loaded, even though only a few threads might actually use the library. Thread-local storage requires support in the ABI, and support in the dynamic linker, if you want reasonable performance. There's a complete description of how it's done on the IA-32, IA-64, and SPARC at http://people.redhat.com/drepper/tls.pdf. This is based on specifications already written for the IA-64 and SPARC; I think the IA-32 implementation is Ulrich Drepper's work. For GDB, the first question is: how should the debugging information describe the location of a thread-local variable? We generally answer this sort of question by looking at how the code generated by the compiler finds the variable, and then emitting debugging information that matches that. To allow the run-time system to allocate thread-local storage on demand, the ABI in certain circumstances requires the compiler to emit a call to a function, __tls_get_addr, to find the address of a thread-local variable for the current thread and a particular module. This function looks up the address in a table, allocates and initializes the storage if necessary, and returns its address. Unfortunately, Dwarf 2 location expressions cannot perform function calls in the inferior. We could extend it to do this, but inferior function calls are rather complicated (look at the *_push_arguments functions and hand_function_call in GDB); I don't think this is a good idea. Instead, I've suggested adding a new Dwarf 2 opcode: 12. DW_OP_push_tls_address The DW_OP_push_tls_address operation pushes the base address of the current thread's thread-local storage block. If the expression occurs in the Dwarf information for a dynamically loaded library, then DW_OP_push_tls_address pushes the base address of that library's block for the current thread. If the library's storage for the current thread has not yet been allocated, a Dwarf consumer may arrange for it to be allocated now, or report an error to the user. When an implementation allocates thread-local storage on demand, this makes it hard to describe the location of a thread-local variable using ordinary Dwarf expressions: referencing the storage may entail allocating memory, copying an initialization image into place, registering it with the thread, and so on. A dedicated operation like DW_OP_push_tls_address leaves this complicated task to the debugger, which is presumably already familiar with the program's ABI and thread system, and can handle the request appropriately. I've posted a note to the Dwarf mailing list, describing the DW_OP_push_tls_address approach, and saying that we'll experiment with this as a GNU extension to Dwarf and write back when we've actually got something working. For STABS, we can simply invent a new symbol type, whose value is the offset within the thread-local storage block for the current thread for the module containing the stab. I haven't written up a real proposal for STABS yet. On Linux, Ulrich Drepper has added the following function to libthread_db: /* Get address of thread local variable. */ extern td_err_e td_thr_tls_get_addr (const td_thrhandle_t *__th, struct link_map *__map, size_t __offset, void **__address); This takes a thread handle, an entry from the dynamic linker's link map, and an offset, and sets *__address to point to the base of that thread and module's thread-local storage, plus the offset. It returns an error code if the space hasn't been allocated yet. Note that this interface is not cross-debugging clean. Actually, none of the libthread_db interface is --- the underlying proc_service interface uses the hosts' `paddr_t' type to represent addresses in the running program, and the top-side interface uses `void *' to represent the location of thread-local data, and in the function above. More on this later. So now we get to the part which isn't really sorted out yet, in my opinion. There's a lot that needs to happen between the point where GDB reads debugging information for a thread-local variable, and the point where GDB can find a thread-local variable's value. GDB already has an address class apparently intended for describing thread-local data. In symtab.h: enum address_class { ... /* Value is at a thread-specific location calculated by a target-specific method. */ LOC_THREAD_LOCAL_STATIC, ... }; It would be more proper for that comment to say that the location is calculated by an ABI-specific method. There needs to be a new gdbarch method: /* return a `struct value' for the object of type TYPE at OFFSET in the thread-local storage block for THREAD and MODULE. If the thread-local storage block hasn't been allocated yet, raise an error. */ struct value *gdbarch_tls_get_value (struct thread_info *thread, struct objfile *module, LONGEST offset, struct type *type); In order for this to reach the thread layer where we can call libthread_db, there will need to be a new target stack method as well, which the gdbarch method can invoke if it pleases. Something similar to the gdbarch method: struct value *(*to_tls_get_addr) (struct thread_info *thread, struct objfile *module, LONGEST offset, struct type *type); The default implementation will raise an error, saying we don't know how to find thread-local storage on this system. The libthread_db target will override that default with something that calls td_thr_tls_get_addr. If you're not convinced it should be a target method, consider this: Remember that libthread_db isn't clean for cross-debugging. It's a target library. So at the moment, there are cases where gdbserver loads and uses libthread_db, not GDB itself. In those cases, the tls_get_addr request needs to be sent across the network connection to gdbserver, td_thr_tls_get_addr needs to be invoked there, and the answer needs to be sent back. By making tls_get_addr a target method, it's easy for the remote protocol layer to provide its own definition of the method and send a packet across for the request.