diff --git a/_drgn.pyi b/_drgn.pyi index 14782ac86..f90f3e4ad 100644 --- a/_drgn.pyi +++ b/_drgn.pyi @@ -733,21 +733,25 @@ class Program: :param lang: :attr:`Type.language` """ ... - def threads(self) -> ThreadIterator: + def threads(self) -> Iterable[Thread]: """ Return an iterable over all of the threads resident within the process. """ ... - def thread(self, tid: int) -> Thread: + def thread(self, tid: IntegerLike) -> Thread: """ - Retrieve the thread with the given `tid`, raising a `LookupError` in the - event no such thread exists. + Retrieve the thread with the given thread ID. + + :param tid: The desired thread's ID (as defined by `gettid(2) `_) + :raises LookupError: if no such thread exists """ ... def crashed_thread(self) -> Thread: """ Return the thread responsible for causing the process to crash (e.g. the thread that segfaulted, received a `SIGQUIT`, etc.). This method should only be called when debugging a core dump. + + :raises ValueError: if the current target is not a core dump """ ... @@ -755,6 +759,10 @@ class ThreadIterator: def __iter__(self) -> Iterable[Thread]: ... class Thread: + """ + A single thread resident within a process. + """ + tid: int """ This thread's ID (as defined by `gettid(2) `_). """ prstatus: bytes @@ -764,6 +772,7 @@ class Thread: def stack_trace(self) -> StackTrace: """ A convenience function for retrieving the stack trace associated with a given thread. + This is equivalent to `prog.stack_trace(thread.tid)`. See the documentation for `Program.stack_trace` for more details. """ diff --git a/drgn/__init__.py b/drgn/__init__.py index 70958673a..7f61388f2 100644 --- a/drgn/__init__.py +++ b/drgn/__init__.py @@ -70,7 +70,7 @@ SymbolBinding, SymbolKind, Thread, - ThreadIterator, + _ThreadIterator, Type, TypeEnumerator, TypeKind, @@ -117,7 +117,7 @@ "SymbolBinding", "SymbolKind", "Thread", - "ThreadIterator", + "_ThreadIterator", "Type", "TypeEnumerator", "TypeKind", diff --git a/libdrgn/drgn.h.in b/libdrgn/drgn.h.in index 2949c9347..8f6666543 100644 --- a/libdrgn/drgn.h.in +++ b/libdrgn/drgn.h.in @@ -2754,4 +2754,75 @@ struct drgn_error *drgn_object_stack_trace(const struct drgn_object *obj, /** @} */ +/** + * @defgroup Threads Threads + * + * Individual threads resident within a process. + * + * @{ + */ + +/** + * @struct drgn_thread + * + * An individual thread resident within a process + */ +struct drgn_thread; + +/** + * @struct drgn_thread_iterator + * + * An iterator over all the threads in a target program. + */ +struct drgn_thread_iterator; + +/** + * Find the thread with the given thread ID. + * + * @param[out] ret The corresponding thread. + * @return @c NULL on success, non-@c NULL on error. + */ + +struct drgn_error *drgn_program_find_thread(struct drgn_program *prog, + uint32_t tid, + struct drgn_thread **ret); + +/** + * Find the thread which caused the process to crash (e.g. the thread that + * segfaulted, received a @c SIGQUIT, etc.). This function should only be + * called when the target is a core dump. + * + * @param[out] ret The thread which caused the process to crash + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog, + struct drgn_thread **ret); + +/** + * Get all of the threads within the given program target. + * + * @param[out] ret An iterator over the threads, which can be advanced with + * @ref drgn_thread_iterator_next, and should be destroyed with @ref + * drgn_thread_iterator_destroy. + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error * +drgn_thread_iterator_create(struct drgn_program *prog, + struct drgn_thread_iterator **ret); + +/** Free a @ref drgn_thread_iterator **/ +void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it); + +/** + * Retrieve the next thread from a @ref drgn_thread_iterator. + * + * @param[out] ret The next thread from the iterator. If its value is @c NULL, + * then this iterator has been exhausted. + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it, + struct drgn_thread **ret); + +/** @} */ + #endif /* DRGN_H */ diff --git a/libdrgn/kdump.c b/libdrgn/kdump.c index 6e2b6e650..82fd9f5bf 100644 --- a/libdrgn/kdump.c +++ b/libdrgn/kdump.c @@ -208,9 +208,13 @@ struct drgn_error *drgn_program_cache_threads_kdump(struct drgn_program *prog) prstatus_data = kdump_blob_pin(prstatus_attr.val.blob); prstatus_size = kdump_blob_size(prstatus_attr.val.blob); + // TODO: Once threads are properly implemented for kernel + // core dumps, ensure that prog->crashed_thread is set correctly + uint32_t _; err = drgn_program_cache_thread(prog, prstatus_data, - prstatus_size); + prstatus_size, + &_); if (err) return err; } diff --git a/libdrgn/program.c b/libdrgn/program.c index b00d09495..95642fe1d 100644 --- a/libdrgn/program.c +++ b/libdrgn/program.c @@ -28,7 +28,17 @@ #include "vector.h" #include "util.h" +static inline uint32_t drgn_thread_to_key(const struct drgn_thread *entry) +{ + return entry->tid; +} + DEFINE_VECTOR_FUNCTIONS(drgn_prstatus_vector) +DEFINE_HASH_TABLE_FUNCTIONS(drgn_thread_set, drgn_thread_to_key, int_key_hash_pair, scalar_key_eq) + +struct drgn_thread_iterator { + struct drgn_thread_set_iterator iterator; +}; static Elf_Type note_header_type(GElf_Phdr *phdr) { @@ -88,7 +98,7 @@ void drgn_program_deinit(struct drgn_program *prog) if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) drgn_prstatus_vector_deinit(&prog->prstatus_vector); else - drgn_thread_map_deinit(&prog->thread_map); + drgn_thread_set_deinit(&prog->thread_set); } free(prog->pgtable_it); @@ -651,7 +661,8 @@ static struct drgn_error *get_prstatus_pid(struct drgn_program *prog, const char struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog, const char *data, - size_t size) + size_t size, + uint32_t *ret) { if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) { struct nstring *entry = @@ -661,21 +672,12 @@ struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog, entry->str = data; entry->len = size; } else { - struct drgn_thread_map_entry entry; - struct drgn_error *err = get_prstatus_pid(prog, data, size, &entry.key); - if (err) - return err; - struct drgn_thread *thread = malloc(sizeof(struct drgn_thread)); - *thread = (struct drgn_thread){ .tid = entry.key, .prstatus = { data, size } }; - if (drgn_thread_map_size(&prog->thread_map) == 0) { - // Having perused the GDB, BFD, and Kernel source code, it appears that - // the first thread listed in a core dump is always the thread that caused - // the process to crash. - prog->crashed_thread = thread; - } - entry.value = thread; - if (drgn_thread_map_insert(&prog->thread_map, &entry, - NULL) == -1) + struct drgn_thread thread = { .prstatus = { data, size } }; + struct drgn_error *err = get_prstatus_pid(prog, data, size, &thread.tid); + if (err) + return err; + *ret = thread.tid; + if (drgn_thread_set_insert(&prog->thread_set, &thread, NULL) == -1) return &drgn_enomem; } return NULL; @@ -685,6 +687,7 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog) { struct drgn_error *err; size_t phnum, i; + uint32_t crashed_thread_tid = -1; if (prog->threads_cached) return NULL; @@ -692,7 +695,7 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog) if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) drgn_prstatus_vector_init(&prog->prstatus_vector); else - drgn_thread_map_init(&prog->thread_map); + drgn_thread_set_init(&prog->thread_set); #ifdef WITH_LIBKDUMPFILE if (prog->kdump_ctx) { @@ -742,11 +745,17 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog) nhdr.n_type != NT_PRSTATUS) continue; + uint32_t tid; err = drgn_program_cache_thread(prog, (char *)data->d_buf + desc_offset, - nhdr.n_descsz); + nhdr.n_descsz, &tid); if (err) goto out; + // Having perused the GDB, BFD, and Kernel source code, it appears that + // the first thread listed in a core dump is always the thread that caused + // the process to crash. + if (i == 0) + crashed_thread_tid = tid; } } @@ -756,9 +765,18 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog) if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) drgn_prstatus_vector_deinit(&prog->prstatus_vector); else - drgn_thread_map_deinit(&prog->thread_map); + drgn_thread_set_deinit(&prog->thread_set); } else { prog->threads_cached = true; + assert(crashed_thread_tid != -1); + // The reason we record the tid and do the lookup at the end is because + // we want to ensure the pointer we use is stable, which wouldn't be the case + // if we set prog->crashed_thread and then continued to insert entries into + // prog->thread_set. + struct drgn_thread_set_iterator it = + drgn_thread_set_search(&prog->thread_set, &crashed_thread_tid); + assert(it.entry); + prog->crashed_thread = it.entry; } return err; } @@ -802,33 +820,37 @@ struct drgn_error *drgn_program_find_prstatus_by_tid(struct drgn_program *prog, return NULL; } -struct drgn_error *drgn_program_find_thread(struct drgn_program *prog, - uint32_t tid, - struct drgn_thread **ret) +LIBDRGN_PUBLIC struct drgn_error * +drgn_program_find_thread(struct drgn_program *prog, uint32_t tid, + struct drgn_thread **ret) { struct drgn_error *err; - struct drgn_thread_map_iterator it; + struct drgn_thread_set_iterator it; assert(!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)); err = drgn_program_cache_threads(prog); if (err) return err; - it = drgn_thread_map_search(&prog->thread_map, &tid); + it = drgn_thread_set_search(&prog->thread_set, &tid); if (!it.entry) { *ret = NULL; return NULL; } - *ret = it.entry->value; + *ret = it.entry; return NULL; } -struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog, - struct drgn_thread **ret) +LIBDRGN_PUBLIC struct drgn_error * +drgn_program_crashed_thread(struct drgn_program *prog, struct drgn_thread **ret) { struct drgn_error *err; assert(!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)); + if (prog->flags & DRGN_PROGRAM_IS_LIVE) + return drgn_error_create( + DRGN_ERROR_INVALID_ARGUMENT, + "the crashed thread can only be found when debugging a core dump"); err = drgn_program_cache_threads(prog); if (err) return err; @@ -836,7 +858,7 @@ struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog, return NULL; } -struct drgn_error * +LIBDRGN_PUBLIC struct drgn_error * drgn_thread_iterator_create(struct drgn_program *prog, struct drgn_thread_iterator **ret) { @@ -845,8 +867,8 @@ drgn_thread_iterator_create(struct drgn_program *prog, if (err) return err; - struct drgn_thread_map_iterator it = - drgn_thread_map_first(&prog->thread_map); + struct drgn_thread_set_iterator it = + drgn_thread_set_first(&prog->thread_set); *ret = malloc(sizeof(struct drgn_thread_iterator)); if (!*ret) return &drgn_enomem; @@ -854,21 +876,23 @@ drgn_thread_iterator_create(struct drgn_program *prog, return NULL; } -void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it) +LIBDRGN_PUBLIC void +drgn_thread_iterator_destroy(struct drgn_thread_iterator *it) { free(it); } -struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it, - struct drgn_thread **ret) +LIBDRGN_PUBLIC struct drgn_error * +drgn_thread_iterator_next(struct drgn_thread_iterator *it, + struct drgn_thread **ret) { if (!it->iterator.entry) { *ret = NULL; return NULL; } - *ret = (it->iterator.entry->value); - it->iterator = drgn_thread_map_next(it->iterator); + *ret = (it->iterator.entry); + it->iterator = drgn_thread_set_next(it->iterator); return NULL; } diff --git a/libdrgn/program.h b/libdrgn/program.h index 8c24f62e6..bbab644c7 100644 --- a/libdrgn/program.h +++ b/libdrgn/program.h @@ -65,17 +65,16 @@ struct vmcoreinfo { }; struct drgn_thread { - int tid; + uint32_t tid; struct nstring prstatus; struct drgn_object thread; }; DEFINE_VECTOR_TYPE(drgn_typep_vector, struct drgn_type *) DEFINE_VECTOR_TYPE(drgn_prstatus_vector, struct nstring) -DEFINE_HASH_MAP_TYPE(drgn_thread_map, uint32_t, struct drgn_thread*) -DEFINE_HASH_MAP_FUNCTIONS(drgn_thread_map, int_key_hash_pair, scalar_key_eq) +DEFINE_HASH_TABLE_TYPE(drgn_thread_set, struct drgn_thread) -struct drgn_thread_iterator { struct drgn_thread_map_iterator iterator; }; +struct drgn_thread_iterator; struct drgn_program { /** @privatesection */ @@ -151,8 +150,8 @@ struct drgn_program { * map. */ struct drgn_prstatus_vector prstatus_vector; - /* For userspace programs, PRSTATUS notes indexed by PID. */ - struct drgn_thread_map thread_map; + /* For userspace programs, threads indexed by PID. */ + struct drgn_thread_set thread_set; }; struct drgn_thread *crashed_thread; bool threads_cached; @@ -292,30 +291,17 @@ struct drgn_error *drgn_program_find_prstatus_by_tid(struct drgn_program *prog, uint32_t tid, struct nstring *ret); -struct drgn_error *drgn_program_find_thread(struct drgn_program *prog, - uint32_t tid, - struct drgn_thread **ret); - -struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog, - struct drgn_thread **ret); - -struct drgn_error * -drgn_thread_iterator_create(struct drgn_program *prog, - struct drgn_thread_iterator **ret); - -void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it); - -struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it, - struct drgn_thread **ret); /** * Cache the @c NT_PRSTATUS note provided by @p data in @p prog. * - * @param[in] data The pointer to the note data. - * @param[in] size Size of data in note. + * @param[in] data The pointer to the note data. + * @param[in] size Size of data in note. + * @param[out] ret The tid of the cached thread */ struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog, const char *data, - size_t size); + size_t size, + uint32_t *ret); /* * Like @ref drgn_program_find_symbol_by_address(), but @p ret is already diff --git a/libdrgn/python/drgnpy.h b/libdrgn/python/drgnpy.h index d5377827b..057a067bf 100644 --- a/libdrgn/python/drgnpy.h +++ b/libdrgn/python/drgnpy.h @@ -243,7 +243,9 @@ Program *program_from_pid(PyObject *self, PyObject *args, PyObject *kwds); PyObject *Symbol_wrap(struct drgn_symbol *sym, Program *prog); -Thread *create_thread(Program *program, struct drgn_thread *drgn_thread); +Thread *Thread_wrap(struct drgn_thread *drgn_thread, Program *prog); + +StackTrace *StackTrace_wrap(struct drgn_stack_trace *trace); static inline Program *DrgnType_prog(DrgnType *type) { @@ -262,7 +264,7 @@ DrgnType *Program_typedef_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_pointer_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_array_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_function_type(Program *self, PyObject *args, PyObject *kwds); -Thread *Program_thread(Program *self, PyObject *tid); +Thread *Program_thread(Program *self, PyObject *args, PyObject *kwds); ThreadIterator *Program_threads(Program *self); Thread *Program_crashed_thread(Program *self); diff --git a/libdrgn/python/program.c b/libdrgn/python/program.c index 30268c4a3..8ca0661c3 100644 --- a/libdrgn/python/program.c +++ b/libdrgn/python/program.c @@ -698,7 +698,6 @@ static DrgnObject *Program_variable(Program *self, PyObject *args, DRGN_FIND_OBJECT_VARIABLE); } - static StackTrace *Program_stack_trace(Program *self, PyObject *args, PyObject *kwds) { @@ -706,7 +705,6 @@ static StackTrace *Program_stack_trace(Program *self, PyObject *args, struct drgn_error *err; PyObject *thread; struct drgn_stack_trace *trace; - StackTrace *ret; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:stack_trace", keywords, &thread)) @@ -725,14 +723,7 @@ static StackTrace *Program_stack_trace(Program *self, PyObject *args, if (err) return set_drgn_error(err); - ret = (StackTrace *)StackTrace_type.tp_alloc(&StackTrace_type, 0); - if (!ret) { - drgn_stack_trace_destroy(trace); - return NULL; - } - ret->trace = trace; - Py_INCREF(self); - return ret; + return StackTrace_wrap(trace); } static PyObject *Program_symbol(Program *self, PyObject *arg) @@ -932,10 +923,11 @@ static PyMethodDef Program_methods[] = { METH_VARARGS | METH_KEYWORDS, drgn_Program_array_type_DOC}, {"function_type", (PyCFunction)Program_function_type, METH_VARARGS | METH_KEYWORDS, drgn_Program_function_type_DOC}, - {"thread", (PyCFunction)Program_thread, METH_O, drgn_Program_thread_DOC }, - {"threads", (PyCFunction)Program_threads, METH_NOARGS, drgn_Program_threads_DOC }, + {"thread", (PyCFunction)Program_thread, + METH_VARARGS | METH_KEYWORDS, drgn_Program_thread_DOC}, + {"threads", (PyCFunction)Program_threads, METH_NOARGS, drgn_Program_threads_DOC}, {"crashed_thread", (PyCFunction)Program_crashed_thread, METH_NOARGS, - drgn_Program_crashed_thread_DOC }, + drgn_Program_crashed_thread_DOC}, {}, }; @@ -1048,12 +1040,16 @@ Program *program_from_pid(PyObject *self, PyObject *args, PyObject *kwds) ThreadIterator *Program_threads(Program *self) { - Py_INCREF(self); ThreadIterator *iterator = (ThreadIterator *)ThreadIterator_type.tp_alloc( &ThreadIterator_type, 0); + if (!iterator) + return NULL; + Py_INCREF(self); iterator->prog = self; - drgn_thread_iterator_create(&self->prog, &iterator->iterator); + struct drgn_error *err = drgn_thread_iterator_create(&self->prog, &iterator->iterator); + if (err) + return set_drgn_error(err); return iterator; } @@ -1066,26 +1062,26 @@ Thread *Program_crashed_thread(Program *self) if (err) return set_drgn_error(err); - return create_thread(self, drgn_thread); + return Thread_wrap(drgn_thread, self); } -Thread *Program_thread(Program *self, PyObject *tid) +Thread *Program_thread(Program *self, PyObject *args, PyObject *kwds) { + static char *keywords[] = { "tid", NULL }; struct drgn_error *err; + struct index_arg tid = {}; struct drgn_thread *drgn_thread; - if (!PyLong_Check(tid)) { - return set_drgn_error(drgn_error_format( - DRGN_ERROR_TYPE, "expected argument of type int, received %s", - tid->ob_type->tp_name)); - } - long tid_value = PyLong_AsLong(tid); - err = drgn_program_find_thread(&self->prog, tid_value, &drgn_thread); + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", keywords, index_converter, + &tid)) + return NULL; + + err = drgn_program_find_thread(&self->prog, tid.uvalue, &drgn_thread); if (err) return set_drgn_error(err); if (drgn_thread == NULL) - return set_drgn_error(drgn_error_format( - DRGN_ERROR_LOOKUP, "thread with tid of %ld not found", tid_value)); - - return create_thread(self, drgn_thread); + return (Thread *)PyErr_Format(PyExc_LookupError, + "thread with tid of %ld not found", + tid.uvalue); + return Thread_wrap(drgn_thread, self); } diff --git a/libdrgn/python/stack_trace.c b/libdrgn/python/stack_trace.c index c0556e62a..f524d13ca 100644 --- a/libdrgn/python/stack_trace.c +++ b/libdrgn/python/stack_trace.c @@ -5,6 +5,19 @@ #include "../stack_trace.h" #include "../util.h" +StackTrace *StackTrace_wrap(struct drgn_stack_trace *trace) { + StackTrace *stack_trace = + (StackTrace *)StackTrace_type.tp_alloc(&StackTrace_type, 0); + if (!stack_trace) { + drgn_stack_trace_destroy(trace); + return NULL; + } + struct drgn_program *prog = trace->prog; + Py_XINCREF(container_of(prog, Program, prog)); + stack_trace->trace = trace; + return stack_trace; +} + static void StackTrace_dealloc(StackTrace *self) { struct drgn_program *prog = self->trace->prog; diff --git a/libdrgn/python/thread.c b/libdrgn/python/thread.c index 7648a6d8a..d5158efb2 100644 --- a/libdrgn/python/thread.c +++ b/libdrgn/python/thread.c @@ -8,13 +8,13 @@ static void Thread_dealloc(Thread *self) static PyLongObject *Thread_get_tid(Thread *self) { - return (PyLongObject *)PyLong_FromLong(self->thread->tid); + return (PyLongObject *)PyLong_FromUnsignedLong(self->thread->tid); } -static PyByteArrayObject *Thread_get_prstatus(Thread *self) +static PyObject *Thread_get_prstatus(Thread *self) { - return (PyByteArrayObject *)PyByteArray_FromStringAndSize( - self->thread->prstatus.str, self->thread->prstatus.len); + return PyBytes_FromStringAndSize(self->thread->prstatus.str, + self->thread->prstatus.len); } static StackTrace *Thread_stack_trace(Thread *self) @@ -25,18 +25,17 @@ static StackTrace *Thread_stack_trace(Thread *self) &trace); if (err) return set_drgn_error(err); - StackTrace *stack_trace = - (StackTrace *)StackTrace_type.tp_alloc(&StackTrace_type, 0); - stack_trace->trace = trace; - return stack_trace; + return StackTrace_wrap(trace); } -Thread *create_thread(Program *program, struct drgn_thread *drgn_thread) +Thread *Thread_wrap(struct drgn_thread *drgn_thread, Program *prog) { Thread *thread = (Thread *)Thread_type.tp_alloc(&Thread_type, 0); + if (!thread) + return NULL; thread->thread = drgn_thread; - Py_INCREF(program); - thread->prog = program; + Py_INCREF(prog); + thread->prog = prog; return thread; } @@ -58,6 +57,7 @@ PyTypeObject Thread_type = { .tp_basicsize = sizeof(Thread), .tp_dealloc = (destructor)Thread_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = drgn_Thread_DOC, .tp_getset = Thread_getset, .tp_methods = Thread_methods, }; @@ -77,12 +77,12 @@ static Thread *ThreadIterator_next(ThreadIterator *self) return set_drgn_error(err); if (drgn_thread == NULL) return NULL; - return create_thread(self->prog, drgn_thread); + return Thread_wrap(drgn_thread, self->prog); } PyTypeObject ThreadIterator_type = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_drgn.ThreadIterator", + .tp_name = "_drgn._ThreadIterator", .tp_basicsize = sizeof(ThreadIterator), .tp_dealloc = (destructor)ThreadIterator_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT,