Problem Statement

I'm trying to get the address of a running thread's start_routine as passed in the pthread_create() call.

Research so far

It is apparently not in /proc/[tid]/stat or /proc/[tid]/status.

I found that start_routine is a member of struct pthread and gets set by pthread_create[1]. If I knew the address of this struct, I could read the start_routine address.

I also found td_thr_get_info defined in the debugging library thread_db.h [2]. It fills a struct with information about the thread, including the start function [3]. But, it needs a struct td_thragent as an argument and I don't know how to create it properly.


the start routine is called from pthread_start_thread() by an indirect call like

call [reg32 + const]  

you can disassemble the function and confirm the register used and the constant used

in my dsl vm (very very old )

i see call [esi+0x8c]

gdb -q ./someexe
break main
break pthread_start_thread
info symbol *( *(unsigned long *)($ebp +8) + 0x8c ) )

provides me the symbol thrdfunc () for the following test program

#include <pthread.h>
void *thrdfunc (void * foo) {
int main (void) {
pthread_t thread;
return 0;

compiled and linked with

gcc -pthread -o pthreadtest pthreadtext.c

I'd be inclined to just use gdb to attach to the process, issue an info threads, select the thread with thread nn and do a bt. The function you want is the one called by start_thread(). Although, this will be in the start function, not the entry itself (although you could prob scan for a well known function prologue).

  • +1 And of course, anything GDB can do, one can do with the ptrace APIs. – Jonathon Reinhart Apr 4 '14 at 0:01

Thanks to the hints of blabb and Jonathon Reinhart I was able to write a get_thread_start_address() function. It reads the same address used by pthread_start_thread() to call the start routine. In Kernel 3.2.0-4-686-pae this address is GS+0x234. I use ptrace to get the GS register and the actual GS segment address. Here is my code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <asm/ldt.h>
#include <asm/ptrace.h>

int attach(int tid);
int detach(int tid);
void print_error(char* func_name, int errnumber);

int get_gs_register(int tid){
    struct user_regs_struct regs;
    int ret = ptrace(PTRACE_GETREGS, tid, NULL, &regs);
    if(ret == -1){
        print_error("PTRACE_GETREGS", errno);
        return -1;
    return regs.xgs;

// This is needed to get the actual GS segment address from the GS register value
int get_thread_area_base(tid, gs){
    struct user_desc desc;
    memset(&desc, 0, sizeof(desc));
    int ret = ptrace(PTRACE_GET_THREAD_AREA, tid, gs / LDT_ENTRY_SIZE, &desc);
    if(ret == -1){
        print_error("PTRACE_GET_THREAD_AREA", errno);
        return -1;
    return desc.base_addr;

void* get_start_address(tid, start_address_pointer){
    char start_addr_str[4];
    int mem_file;
    char mem_file_path[255];
    snprintf(mem_file_path, sizeof(mem_file_path), "/proc/%i/mem", tid);
    mem_file = open(mem_file_path, O_RDONLY);
    if(mem_file == -1){
        print_error("open()", errno);
        return (void*) -4;
    int ret = lseek(mem_file, start_address_pointer, SEEK_SET);
    if(ret == -1){
        print_error("lseek()", errno);
        return (void*) -5;

    ret = read(mem_file, start_addr_str, 4);
    if(ret == -1){
        print_error("read()", errno);
        return (void*) -6;

    return (void*) *((int*)start_addr_str);

int main(int argc, char* argv[]){
    if(argc <= 1){
        printf("Usage: %s TID\n", argv[0]);
        return -1;
    int tid = atoi(argv[1]);    
    int gs;
    int thread_area_base;
    int start_address_offset = 0x234;
    void* start_address;

    int ret = attach(tid);
    if(ret == -1) return -1;

    gs = get_gs_register(tid);
    if(gs == -1){
        return -2;

    thread_area_base = get_thread_area_base(tid, gs);
    if(thread_area_base == -1){
        return -3;
    printf("thread_area_base: %p\n", (void*) thread_area_base);
    unsigned int start_address_pointer = thread_area_base + start_address_offset;
    printf("start_address_pointer: %p\n", (void*) start_address_pointer);

    start_address = get_start_address(tid, start_address_pointer);
    printf("start_address: %p\n", start_address);

    return 0;

int attach(int tid){
    int status; 
    int ret = ptrace(PTRACE_ATTACH, tid, NULL, NULL);
    if(ret == -1){
        print_error("PTRACE_ATTACH", errno);

    ret = waitpid(-1, &status, __WALL);
    if(ret == -1){
        print_error("waitpid()", errno);
    return ret;

int detach(int tid){
    int ret = ptrace(PTRACE_DETACH, tid, NULL, NULL);
    if(ret == -1){
        print_error("PTRACE_DETACH", errno);
    return ret;

void print_error(char* func_name, int errnumber){
    printf("%s failed. %i, %s\n", func_name, errnumber, strerror(errnumber));

Since all threads share the address space of the process that created them, I think what you want is a call to dlsym:


The dlsym() function shall obtain the address of a symbol defined within an object made accessible through a dlopen() call. The handle argument is the value returned from a call to dlopen() (and which has not since been released via a call to dlclose()), and name is the symbol's name as a character string.

=======NEXT ATTEMPT========

Started poking around in pthread.h. The best thing I've found so far is this:


The stack attributes specify the area of storage to be used for the created thread's stack. The base (lowest addressable byte) of the storage shall be stackaddr, and the size of the storage shall be stacksize bytes. The stacksize shall be at least {PTHREAD_STACK_MIN}. The stackaddr shall be aligned appropriately to be used as a stack; for example, pthread_attr_setstack() may fail with [EINVAL] if ( stackaddr & 0x7) is not 0. All pages within the stack described by stackaddr and stacksize shall be both readable and writable by the thread.

There's more on POSIX thread programming here.

This is the closest functionality to NtQueryInformationThread I could find.

  • Thanks for your answer. I actually want to get the start address at runtime. And I don't see the danger in providing it. There is a function for this in Windows and I don't know of any attack using it. – user2804197 Apr 1 '14 at 23:14
  • If you expose a function to compute a thread's start address, why bother with ASLR in the first place? ASLR was made to block being able to predict where code was loaded. If you expose a function like that... you just defeated ASLR. Anyway, I think I might have a real answer soon... – avgvstvs Apr 2 '14 at 13:24
  • Updated answer. – avgvstvs Apr 2 '14 at 13:29
  • "ASLR was made to block being able to predict where code was loaded. If you expose a function like that... you just defeated ASLR." IMO the goal of ASLR is to prevent an attacker to execute arbitrary code in the first place by making the address of return addresses and libraries unknown. How would an attacker get the address of a get_start_address() function to circumvent ASLR? – user2804197 Apr 3 '14 at 15:00
  • 1
    I don't see how you can get a thread's start address with dlsym(). It returns a symbol's address of a shared object. Starting a thread doesn't create or alter any shared object, so it's start address can't be extracted from a shared object. Or am I missing something? Anyway, thanks for trying to help me. – user2804197 Apr 3 '14 at 15:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.